diff options
Diffstat (limited to 'Libraries/LibCore')
44 files changed, 2815 insertions, 0 deletions
diff --git a/Libraries/LibCore/CArgsParser.cpp b/Libraries/LibCore/CArgsParser.cpp new file mode 100644 index 0000000000..12ce7c90b4 --- /dev/null +++ b/Libraries/LibCore/CArgsParser.cpp @@ -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()); +} diff --git a/Libraries/LibCore/CArgsParser.h b/Libraries/LibCore/CArgsParser.h new file mode 100644 index 0000000000..6a696054e2 --- /dev/null +++ b/Libraries/LibCore/CArgsParser.h @@ -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; +}; diff --git a/Libraries/LibCore/CConfigFile.cpp b/Libraries/LibCore/CConfigFile.cpp new file mode 100644 index 0000000000..2f1fec736a --- /dev/null +++ b/Libraries/LibCore/CConfigFile.cpp @@ -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; +} diff --git a/Libraries/LibCore/CConfigFile.h b/Libraries/LibCore/CConfigFile.h new file mode 100644 index 0000000000..1816ba53c9 --- /dev/null +++ b/Libraries/LibCore/CConfigFile.h @@ -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 }; +}; diff --git a/Libraries/LibCore/CDirIterator.cpp b/Libraries/LibCore/CDirIterator.cpp new file mode 100644 index 0000000000..d76d52970d --- /dev/null +++ b/Libraries/LibCore/CDirIterator.cpp @@ -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; +} diff --git a/Libraries/LibCore/CDirIterator.h b/Libraries/LibCore/CDirIterator.h new file mode 100644 index 0000000000..08c9040b48 --- /dev/null +++ b/Libraries/LibCore/CDirIterator.h @@ -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(); +}; diff --git a/Libraries/LibCore/CElapsedTimer.cpp b/Libraries/LibCore/CElapsedTimer.cpp new file mode 100644 index 0000000000..4e8902d348 --- /dev/null +++ b/Libraries/LibCore/CElapsedTimer.cpp @@ -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; +} diff --git a/Libraries/LibCore/CElapsedTimer.h b/Libraries/LibCore/CElapsedTimer.h new file mode 100644 index 0000000000..76fa304478 --- /dev/null +++ b/Libraries/LibCore/CElapsedTimer.h @@ -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 + }; +}; diff --git a/Libraries/LibCore/CEvent.cpp b/Libraries/LibCore/CEvent.cpp new file mode 100644 index 0000000000..eacce964c2 --- /dev/null +++ b/Libraries/LibCore/CEvent.cpp @@ -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() +{ +} diff --git a/Libraries/LibCore/CEvent.h b/Libraries/LibCore/CEvent.h new file mode 100644 index 0000000000..9d262c4a76 --- /dev/null +++ b/Libraries/LibCore/CEvent.h @@ -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; +}; diff --git a/Libraries/LibCore/CEventLoop.cpp b/Libraries/LibCore/CEventLoop.cpp new file mode 100644 index 0000000000..e38c2b2154 --- /dev/null +++ b/Libraries/LibCore/CEventLoop.cpp @@ -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(¬ifier); +} + +void CEventLoop::unregister_notifier(Badge<CNotifier>, CNotifier& notifier) +{ + s_notifiers->remove(¬ifier); +} diff --git a/Libraries/LibCore/CEventLoop.h b/Libraries/LibCore/CEventLoop.h new file mode 100644 index 0000000000..8cab0bbe99 --- /dev/null +++ b/Libraries/LibCore/CEventLoop.h @@ -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; +}; diff --git a/Libraries/LibCore/CFile.cpp b/Libraries/LibCore/CFile.cpp new file mode 100644 index 0000000000..09d209d4b4 --- /dev/null +++ b/Libraries/LibCore/CFile.cpp @@ -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; +} diff --git a/Libraries/LibCore/CFile.h b/Libraries/LibCore/CFile.h new file mode 100644 index 0000000000..976d5718d9 --- /dev/null +++ b/Libraries/LibCore/CFile.h @@ -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 }; +}; diff --git a/Libraries/LibCore/CHttpJob.cpp b/Libraries/LibCore/CHttpJob.cpp new file mode 100644 index 0000000000..a3b00b9175 --- /dev/null +++ b/Libraries/LibCore/CHttpJob.cpp @@ -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); +} diff --git a/Libraries/LibCore/CHttpJob.h b/Libraries/LibCore/CHttpJob.h new file mode 100644 index 0000000000..5b714a7f77 --- /dev/null +++ b/Libraries/LibCore/CHttpJob.h @@ -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; +}; diff --git a/Libraries/LibCore/CHttpRequest.cpp b/Libraries/LibCore/CHttpRequest.cpp new file mode 100644 index 0000000000..93cbc6987d --- /dev/null +++ b/Libraries/LibCore/CHttpRequest.cpp @@ -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(); +} diff --git a/Libraries/LibCore/CHttpRequest.h b/Libraries/LibCore/CHttpRequest.h new file mode 100644 index 0000000000..c5a39c9ffd --- /dev/null +++ b/Libraries/LibCore/CHttpRequest.h @@ -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 }; +}; diff --git a/Libraries/LibCore/CHttpResponse.cpp b/Libraries/LibCore/CHttpResponse.cpp new file mode 100644 index 0000000000..07fef16ee0 --- /dev/null +++ b/Libraries/LibCore/CHttpResponse.cpp @@ -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() +{ +} diff --git a/Libraries/LibCore/CHttpResponse.h b/Libraries/LibCore/CHttpResponse.h new file mode 100644 index 0000000000..3feaa6bfd2 --- /dev/null +++ b/Libraries/LibCore/CHttpResponse.h @@ -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; +}; diff --git a/Libraries/LibCore/CIODevice.cpp b/Libraries/LibCore/CIODevice.cpp new file mode 100644 index 0000000000..86393f6888 --- /dev/null +++ b/Libraries/LibCore/CIODevice.cpp @@ -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; +} diff --git a/Libraries/LibCore/CIODevice.h b/Libraries/LibCore/CIODevice.h new file mode 100644 index 0000000000..e6de17b46d --- /dev/null +++ b/Libraries/LibCore/CIODevice.h @@ -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; +}; diff --git a/Libraries/LibCore/CLock.h b/Libraries/LibCore/CLock.h new file mode 100644 index 0000000000..2c31751f92 --- /dev/null +++ b/Libraries/LibCore/CLock.h @@ -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 diff --git a/Libraries/LibCore/CNetworkJob.cpp b/Libraries/LibCore/CNetworkJob.cpp new file mode 100644 index 0000000000..67d02d5d73 --- /dev/null +++ b/Libraries/LibCore/CNetworkJob.cpp @@ -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)"; + } +} diff --git a/Libraries/LibCore/CNetworkJob.h b/Libraries/LibCore/CNetworkJob.h new file mode 100644 index 0000000000..b68dba5670 --- /dev/null +++ b/Libraries/LibCore/CNetworkJob.h @@ -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); diff --git a/Libraries/LibCore/CNetworkResponse.cpp b/Libraries/LibCore/CNetworkResponse.cpp new file mode 100644 index 0000000000..1550d8ac69 --- /dev/null +++ b/Libraries/LibCore/CNetworkResponse.cpp @@ -0,0 +1,10 @@ +#include <LibCore/CNetworkResponse.h> + +CNetworkResponse::CNetworkResponse(ByteBuffer&& payload) + : m_payload(payload) +{ +} + +CNetworkResponse::~CNetworkResponse() +{ +} diff --git a/Libraries/LibCore/CNetworkResponse.h b/Libraries/LibCore/CNetworkResponse.h new file mode 100644 index 0000000000..c637f6a514 --- /dev/null +++ b/Libraries/LibCore/CNetworkResponse.h @@ -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; +}; diff --git a/Libraries/LibCore/CNotifier.cpp b/Libraries/LibCore/CNotifier.cpp new file mode 100644 index 0000000000..c124191ac1 --- /dev/null +++ b/Libraries/LibCore/CNotifier.cpp @@ -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); +} diff --git a/Libraries/LibCore/CNotifier.h b/Libraries/LibCore/CNotifier.h new file mode 100644 index 0000000000..196cad8388 --- /dev/null +++ b/Libraries/LibCore/CNotifier.h @@ -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 }; +}; diff --git a/Libraries/LibCore/CObject.cpp b/Libraries/LibCore/CObject.cpp new file mode 100644 index 0000000000..bbee4501dd --- /dev/null +++ b/Libraries/LibCore/CObject.cpp @@ -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))); +} diff --git a/Libraries/LibCore/CObject.h b/Libraries/LibCore/CObject.h new file mode 100644 index 0000000000..392a94e189 --- /dev/null +++ b/Libraries/LibCore/CObject.h @@ -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; + }); +} diff --git a/Libraries/LibCore/CProcessStatisticsReader.cpp b/Libraries/LibCore/CProcessStatisticsReader.cpp new file mode 100644 index 0000000000..7f0456d53e --- /dev/null +++ b/Libraries/LibCore/CProcessStatisticsReader.cpp @@ -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); +} diff --git a/Libraries/LibCore/CProcessStatisticsReader.h b/Libraries/LibCore/CProcessStatisticsReader.h new file mode 100644 index 0000000000..adb96f39ad --- /dev/null +++ b/Libraries/LibCore/CProcessStatisticsReader.h @@ -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; +}; diff --git a/Libraries/LibCore/CSocket.cpp b/Libraries/LibCore/CSocket.cpp new file mode 100644 index 0000000000..4479969c40 --- /dev/null +++ b/Libraries/LibCore/CSocket.cpp @@ -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; +} diff --git a/Libraries/LibCore/CSocket.h b/Libraries/LibCore/CSocket.h new file mode 100644 index 0000000000..587f80f290 --- /dev/null +++ b/Libraries/LibCore/CSocket.h @@ -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; +}; diff --git a/Libraries/LibCore/CSocketAddress.h b/Libraries/LibCore/CSocketAddress.h new file mode 100644 index 0000000000..019a0869fb --- /dev/null +++ b/Libraries/LibCore/CSocketAddress.h @@ -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; +}; diff --git a/Libraries/LibCore/CTCPSocket.cpp b/Libraries/LibCore/CTCPSocket.cpp new file mode 100644 index 0000000000..5f3702a482 --- /dev/null +++ b/Libraries/LibCore/CTCPSocket.cpp @@ -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() +{ +} diff --git a/Libraries/LibCore/CTCPSocket.h b/Libraries/LibCore/CTCPSocket.h new file mode 100644 index 0000000000..fe4f3ffb3a --- /dev/null +++ b/Libraries/LibCore/CTCPSocket.h @@ -0,0 +1,9 @@ +#include <LibCore/CSocket.h> + +class CTCPSocket final : public CSocket { +public: + explicit CTCPSocket(CObject* parent = nullptr); + virtual ~CTCPSocket() override; + +private: +}; diff --git a/Libraries/LibCore/CTimer.cpp b/Libraries/LibCore/CTimer.cpp new file mode 100644 index 0000000000..fb55ad7e63 --- /dev/null +++ b/Libraries/LibCore/CTimer.cpp @@ -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(); +} diff --git a/Libraries/LibCore/CTimer.h b/Libraries/LibCore/CTimer.h new file mode 100644 index 0000000000..b6c1c2005f --- /dev/null +++ b/Libraries/LibCore/CTimer.h @@ -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 }; +}; diff --git a/Libraries/LibCore/CUserInfo.cpp b/Libraries/LibCore/CUserInfo.cpp new file mode 100644 index 0000000000..d5308d454e --- /dev/null +++ b/Libraries/LibCore/CUserInfo.cpp @@ -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; +} diff --git a/Libraries/LibCore/CUserInfo.h b/Libraries/LibCore/CUserInfo.h new file mode 100644 index 0000000000..b47cec4af1 --- /dev/null +++ b/Libraries/LibCore/CUserInfo.h @@ -0,0 +1 @@ +const char* get_current_user_home_path(); diff --git a/Libraries/LibCore/Makefile b/Libraries/LibCore/Makefile new file mode 100644 index 0000000000..1f88103724 --- /dev/null +++ b/Libraries/LibCore/Makefile @@ -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 diff --git a/Libraries/LibCore/install.sh b/Libraries/LibCore/install.sh new file mode 100755 index 0000000000..fedc4b73fb --- /dev/null +++ b/Libraries/LibCore/install.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir -p ../Root/usr/include/LibCore/ +cp *.h ../Root/usr/include/LibCore/ +cp libcore.a ../Root/usr/lib/ |