mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 01:07:36 +00:00
Libraries: Move to Userland/Libraries/
This commit is contained in:
parent
dc28c07fa5
commit
13d7c09125
1857 changed files with 266 additions and 274 deletions
316
Userland/Libraries/LibCore/Account.cpp
Normal file
316
Userland/Libraries/LibCore/Account.cpp
Normal file
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Base64.h>
|
||||
#include <AK/Random.h>
|
||||
#include <LibCore/Account.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
static String get_salt()
|
||||
{
|
||||
char random_data[12];
|
||||
AK::fill_with_random(random_data, sizeof(random_data));
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append("$5$");
|
||||
builder.append(encode_base64(ReadonlyBytes(random_data, sizeof(random_data))));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static Vector<gid_t> get_gids(const StringView& username)
|
||||
{
|
||||
Vector<gid_t> extra_gids;
|
||||
for (auto* group = getgrent(); group; group = getgrent()) {
|
||||
for (size_t i = 0; group->gr_mem[i]; ++i) {
|
||||
if (username == group->gr_mem[i]) {
|
||||
extra_gids.append(group->gr_gid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
endgrent();
|
||||
return extra_gids;
|
||||
}
|
||||
|
||||
Result<Account, String> Account::from_passwd(const passwd& pwd, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||
{
|
||||
RefPtr<Core::File> passwd_file;
|
||||
if (open_passwd_file != Core::Account::OpenPasswdFile::No) {
|
||||
auto open_mode = open_passwd_file == Core::Account::OpenPasswdFile::ReadOnly
|
||||
? Core::File::OpenMode::ReadOnly
|
||||
: Core::File::OpenMode::ReadWrite;
|
||||
auto file_or_error = Core::File::open("/etc/passwd", open_mode);
|
||||
if (file_or_error.is_error())
|
||||
return file_or_error.error();
|
||||
passwd_file = file_or_error.value();
|
||||
}
|
||||
|
||||
RefPtr<Core::File> shadow_file;
|
||||
if (open_shadow_file != Core::Account::OpenShadowFile::No) {
|
||||
auto open_mode = open_shadow_file == Core::Account::OpenShadowFile::ReadOnly
|
||||
? Core::File::OpenMode::ReadOnly
|
||||
: Core::File::OpenMode::ReadWrite;
|
||||
auto file_or_error = Core::File::open("/etc/shadow", open_mode);
|
||||
if (file_or_error.is_error())
|
||||
return file_or_error.error();
|
||||
shadow_file = file_or_error.value();
|
||||
}
|
||||
|
||||
Account account(pwd, get_gids(pwd.pw_name), move(passwd_file), move(shadow_file));
|
||||
endpwent();
|
||||
return account;
|
||||
}
|
||||
|
||||
Result<Account, String> Account::from_name(const char* username, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||
{
|
||||
struct passwd* pwd = nullptr;
|
||||
errno = 0;
|
||||
pwd = getpwnam(username);
|
||||
if (!pwd) {
|
||||
if (errno == 0)
|
||||
return String("No such user");
|
||||
|
||||
return String(strerror(errno));
|
||||
}
|
||||
return from_passwd(*pwd, open_passwd_file, open_shadow_file);
|
||||
}
|
||||
|
||||
Result<Account, String> Account::from_uid(uid_t uid, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||
{
|
||||
struct passwd* pwd = nullptr;
|
||||
errno = 0;
|
||||
pwd = getpwuid(uid);
|
||||
if (!pwd) {
|
||||
if (errno == 0)
|
||||
return String("No such user");
|
||||
|
||||
return String(strerror(errno));
|
||||
}
|
||||
return from_passwd(*pwd, open_passwd_file, open_shadow_file);
|
||||
}
|
||||
|
||||
bool Account::authenticate(const char* password) const
|
||||
{
|
||||
// An empty passwd field indicates that no password is required to log in.
|
||||
if (m_password_hash.is_empty())
|
||||
return true;
|
||||
|
||||
// FIXME: Use crypt_r if it can be built in lagom.
|
||||
char* hash = crypt(password, m_password_hash.characters());
|
||||
return hash != nullptr && strcmp(hash, m_password_hash.characters()) == 0;
|
||||
}
|
||||
|
||||
bool Account::login() const
|
||||
{
|
||||
if (setgroups(m_extra_gids.size(), m_extra_gids.data()) < 0)
|
||||
return false;
|
||||
|
||||
if (setgid(m_gid) < 0)
|
||||
return false;
|
||||
|
||||
if (setuid(m_uid) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Account::set_password(const char* password)
|
||||
{
|
||||
m_password_hash = crypt(password, get_salt().characters());
|
||||
}
|
||||
|
||||
void Account::set_password_enabled(bool enabled)
|
||||
{
|
||||
if (enabled && m_password_hash != "" && m_password_hash[0] == '!') {
|
||||
m_password_hash = m_password_hash.substring(1, m_password_hash.length() - 1);
|
||||
} else if (!enabled && (m_password_hash == "" || m_password_hash[0] != '!')) {
|
||||
StringBuilder builder;
|
||||
builder.append('!');
|
||||
builder.append(m_password_hash);
|
||||
m_password_hash = builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
void Account::delete_password()
|
||||
{
|
||||
m_password_hash = "";
|
||||
}
|
||||
|
||||
Account::Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file)
|
||||
: m_passwd_file(move(passwd_file))
|
||||
, m_shadow_file(move(shadow_file))
|
||||
, m_username(pwd.pw_name)
|
||||
, m_uid(pwd.pw_uid)
|
||||
, m_gid(pwd.pw_gid)
|
||||
, m_gecos(pwd.pw_gecos)
|
||||
, m_home_directory(pwd.pw_dir)
|
||||
, m_shell(pwd.pw_shell)
|
||||
, m_extra_gids(extra_gids)
|
||||
{
|
||||
if (m_shadow_file) {
|
||||
load_shadow_file();
|
||||
}
|
||||
}
|
||||
|
||||
String Account::generate_passwd_file() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
setpwent();
|
||||
|
||||
struct passwd* p;
|
||||
errno = 0;
|
||||
while ((p = getpwent())) {
|
||||
if (p->pw_uid == m_uid) {
|
||||
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
|
||||
m_username,
|
||||
m_uid, m_gid,
|
||||
m_gecos,
|
||||
m_home_directory,
|
||||
m_shell);
|
||||
|
||||
} else {
|
||||
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
|
||||
p->pw_name, p->pw_uid,
|
||||
p->pw_gid, p->pw_gecos, p->pw_dir,
|
||||
p->pw_shell);
|
||||
}
|
||||
}
|
||||
endpwent();
|
||||
|
||||
if (errno) {
|
||||
dbgln("errno was non-zero after generating new passwd file.");
|
||||
return {};
|
||||
}
|
||||
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
void Account::load_shadow_file()
|
||||
{
|
||||
ASSERT(m_shadow_file);
|
||||
ASSERT(m_shadow_file->is_open());
|
||||
|
||||
if (!m_shadow_file->seek(0)) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Vector<ShadowEntry> entries;
|
||||
|
||||
for (;;) {
|
||||
auto line = m_shadow_file->read_line();
|
||||
if (line.is_null())
|
||||
break;
|
||||
auto parts = line.split(':');
|
||||
if (parts.size() != 2) {
|
||||
dbgln("Malformed shadow entry, ignoring.");
|
||||
continue;
|
||||
}
|
||||
const auto& username = parts[0];
|
||||
const auto& password_hash = parts[1];
|
||||
entries.append({ username, password_hash });
|
||||
|
||||
if (username == m_username) {
|
||||
m_password_hash = password_hash;
|
||||
}
|
||||
}
|
||||
|
||||
m_shadow_entries = move(entries);
|
||||
}
|
||||
|
||||
String Account::generate_shadow_file() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
bool updated_entry_in_place = false;
|
||||
for (auto& entry : m_shadow_entries) {
|
||||
if (entry.username == m_username) {
|
||||
updated_entry_in_place = true;
|
||||
builder.appendff("{}:{}\n", m_username, m_password_hash);
|
||||
} else {
|
||||
builder.appendff("{}:{}\n", entry.username, entry.password_hash);
|
||||
}
|
||||
}
|
||||
if (!updated_entry_in_place)
|
||||
builder.appendff("{}:{}\n", m_username, m_password_hash);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
bool Account::sync()
|
||||
{
|
||||
ASSERT(m_passwd_file);
|
||||
ASSERT(m_passwd_file->mode() == Core::File::OpenMode::ReadWrite);
|
||||
ASSERT(m_shadow_file);
|
||||
ASSERT(m_shadow_file->mode() == Core::File::OpenMode::ReadWrite);
|
||||
|
||||
// FIXME: Maybe reorganize this to create temporary files and finish it completely before renaming them to /etc/{passwd,shadow}
|
||||
// If truncation succeeds but write fails, we'll have an empty file :(
|
||||
|
||||
auto new_passwd_file = generate_passwd_file();
|
||||
auto new_shadow_file = generate_shadow_file();
|
||||
|
||||
if (new_passwd_file.is_null() || new_shadow_file.is_null()) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (!m_passwd_file->seek(0) || !m_shadow_file->seek(0)) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (!m_passwd_file->truncate(0)) {
|
||||
dbgln("Truncating passwd file failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_passwd_file->write(new_passwd_file)) {
|
||||
// FIXME: Improve Core::File::write() error reporting.
|
||||
dbgln("Writing to passwd file failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_shadow_file->truncate(0)) {
|
||||
dbgln("Truncating shadow file failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_shadow_file->write(new_shadow_file)) {
|
||||
// FIXME: Improve Core::File::write() error reporting.
|
||||
dbgln("Writing to shadow file failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// FIXME: Sync extra groups.
|
||||
}
|
||||
|
||||
}
|
109
Userland/Libraries/LibCore/Account.h
Normal file
109
Userland/Libraries/LibCore/Account.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Result.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Account {
|
||||
public:
|
||||
enum class OpenPasswdFile {
|
||||
No,
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
};
|
||||
|
||||
enum class OpenShadowFile {
|
||||
No,
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
};
|
||||
|
||||
static Result<Account, String> from_name(const char* username, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
|
||||
static Result<Account, String> from_uid(uid_t uid, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
|
||||
|
||||
bool authenticate(const char* password) const;
|
||||
bool login() const;
|
||||
|
||||
String username() const { return m_username; }
|
||||
String password_hash() const { return m_password_hash; }
|
||||
|
||||
// Setters only affect in-memory copy of password.
|
||||
// You must call sync to apply changes.
|
||||
void set_password(const char* password);
|
||||
void set_password_enabled(bool enabled);
|
||||
void delete_password();
|
||||
bool has_password() const { return !m_password_hash.is_empty(); }
|
||||
|
||||
uid_t uid() const { return m_uid; }
|
||||
gid_t gid() const { return m_gid; }
|
||||
const String& gecos() const { return m_gecos; }
|
||||
const String& home_directory() const { return m_home_directory; }
|
||||
const String& shell() const { return m_shell; }
|
||||
const Vector<gid_t>& extra_gids() const { return m_extra_gids; }
|
||||
|
||||
bool sync();
|
||||
|
||||
private:
|
||||
static Result<Account, String> from_passwd(const passwd&, OpenPasswdFile, OpenShadowFile);
|
||||
|
||||
Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file);
|
||||
void load_shadow_file();
|
||||
|
||||
String generate_passwd_file() const;
|
||||
String generate_shadow_file() const;
|
||||
|
||||
RefPtr<Core::File> m_passwd_file;
|
||||
RefPtr<Core::File> m_shadow_file;
|
||||
|
||||
String m_username;
|
||||
|
||||
// Contents of passwd field in passwd entry.
|
||||
// Can be empty, "x", or contain a leading '!'
|
||||
String m_password_hash;
|
||||
uid_t m_uid { 0 };
|
||||
gid_t m_gid { 0 };
|
||||
String m_gecos;
|
||||
String m_home_directory;
|
||||
String m_shell;
|
||||
Vector<gid_t> m_extra_gids;
|
||||
|
||||
struct ShadowEntry {
|
||||
String username;
|
||||
String password_hash;
|
||||
};
|
||||
Vector<ShadowEntry> m_shadow_entries;
|
||||
};
|
||||
|
||||
}
|
392
Userland/Libraries/LibCore/ArgsParser.cpp
Normal file
392
Userland/Libraries/LibCore/ArgsParser.cpp
Normal file
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Format.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr bool isnan(double __x) { return __builtin_isnan(__x); }
|
||||
|
||||
static Optional<double> convert_to_double(const char* s)
|
||||
{
|
||||
char* p;
|
||||
double v = strtod(s, &p);
|
||||
if (isnan(v) || p == s)
|
||||
return {};
|
||||
return v;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
ArgsParser::ArgsParser()
|
||||
{
|
||||
add_option(m_show_help, "Display this message", "help", 0);
|
||||
}
|
||||
|
||||
bool ArgsParser::parse(int argc, char** argv, bool exit_on_failure)
|
||||
{
|
||||
auto print_usage_and_exit = [this, argv, exit_on_failure] {
|
||||
print_usage(stderr, argv[0]);
|
||||
if (exit_on_failure)
|
||||
exit(1);
|
||||
};
|
||||
|
||||
Vector<option> long_options;
|
||||
StringBuilder short_options_builder;
|
||||
|
||||
int index_of_found_long_option = -1;
|
||||
|
||||
// Tell getopt() to reset its internal state, and start scanning from optind = 1.
|
||||
// We could also set optreset = 1, but the host platform may not support that.
|
||||
optind = 0;
|
||||
|
||||
for (size_t i = 0; i < m_options.size(); i++) {
|
||||
auto& opt = m_options[i];
|
||||
if (opt.long_name) {
|
||||
option long_opt {
|
||||
opt.long_name,
|
||||
opt.requires_argument ? required_argument : no_argument,
|
||||
&index_of_found_long_option,
|
||||
static_cast<int>(i)
|
||||
};
|
||||
long_options.append(long_opt);
|
||||
}
|
||||
if (opt.short_name) {
|
||||
short_options_builder.append(opt.short_name);
|
||||
if (opt.requires_argument)
|
||||
short_options_builder.append(':');
|
||||
}
|
||||
}
|
||||
long_options.append({ 0, 0, 0, 0 });
|
||||
|
||||
String short_options = short_options_builder.build();
|
||||
|
||||
while (true) {
|
||||
int c = getopt_long(argc, argv, short_options.characters(), long_options.data(), nullptr);
|
||||
if (c == -1) {
|
||||
// We have reached the end.
|
||||
break;
|
||||
} else if (c == '?') {
|
||||
// There was an error, and getopt() has already
|
||||
// printed its error message.
|
||||
print_usage_and_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's see what option we just found.
|
||||
Option* found_option = nullptr;
|
||||
if (c == 0) {
|
||||
// It was a long option.
|
||||
ASSERT(index_of_found_long_option >= 0);
|
||||
found_option = &m_options[index_of_found_long_option];
|
||||
index_of_found_long_option = -1;
|
||||
} else {
|
||||
// It was a short option, look it up.
|
||||
auto it = m_options.find_if([c](auto& opt) { return c == opt.short_name; });
|
||||
ASSERT(!it.is_end());
|
||||
found_option = &*it;
|
||||
}
|
||||
ASSERT(found_option);
|
||||
|
||||
const char* arg = found_option->requires_argument ? optarg : nullptr;
|
||||
if (!found_option->accept_value(arg)) {
|
||||
warnln("\033[31mInvalid value for option \033[1m{}\033[22m, dude\033[0m", found_option->name_for_display());
|
||||
print_usage_and_exit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We're done processing options, now let's parse positional arguments.
|
||||
|
||||
int values_left = argc - optind;
|
||||
int num_values_for_arg[m_positional_args.size()];
|
||||
int total_values_required = 0;
|
||||
for (size_t i = 0; i < m_positional_args.size(); i++) {
|
||||
auto& arg = m_positional_args[i];
|
||||
num_values_for_arg[i] = arg.min_values;
|
||||
total_values_required += arg.min_values;
|
||||
}
|
||||
|
||||
if (total_values_required > values_left) {
|
||||
print_usage_and_exit();
|
||||
return false;
|
||||
}
|
||||
int extra_values_to_distribute = values_left - total_values_required;
|
||||
|
||||
for (size_t i = 0; i < m_positional_args.size(); i++) {
|
||||
auto& arg = m_positional_args[i];
|
||||
int extra_values_to_this_arg = min(arg.max_values - arg.min_values, extra_values_to_distribute);
|
||||
num_values_for_arg[i] += extra_values_to_this_arg;
|
||||
extra_values_to_distribute -= extra_values_to_this_arg;
|
||||
if (extra_values_to_distribute == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (extra_values_to_distribute > 0) {
|
||||
// We still have too many values :(
|
||||
print_usage_and_exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_positional_args.size(); i++) {
|
||||
auto& arg = m_positional_args[i];
|
||||
for (int j = 0; j < num_values_for_arg[i]; j++) {
|
||||
const char* value = argv[optind++];
|
||||
if (!arg.accept_value(value)) {
|
||||
warnln("Invalid value for argument {}", arg.name);
|
||||
print_usage_and_exit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're done parsing! :)
|
||||
// Now let's show help if requested.
|
||||
if (m_show_help) {
|
||||
print_usage(stdout, argv[0]);
|
||||
if (exit_on_failure)
|
||||
exit(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ArgsParser::print_usage(FILE* file, const char* argv0)
|
||||
{
|
||||
out(file, "Usage:\n\t\033[1m{}\033[0m", argv0);
|
||||
|
||||
for (auto& opt : m_options) {
|
||||
if (opt.long_name && !strcmp(opt.long_name, "help"))
|
||||
continue;
|
||||
if (opt.requires_argument)
|
||||
out(file, " [{} {}]", opt.name_for_display(), opt.value_name);
|
||||
else
|
||||
out(file, " [{}]", opt.name_for_display());
|
||||
}
|
||||
for (auto& arg : m_positional_args) {
|
||||
bool required = arg.min_values > 0;
|
||||
bool repeated = arg.max_values > 1;
|
||||
|
||||
if (required && repeated)
|
||||
out(file, " <{}...>", arg.name);
|
||||
else if (required && !repeated)
|
||||
out(file, " <{}>", arg.name);
|
||||
else if (!required && repeated)
|
||||
out(file, " [{}...]", arg.name);
|
||||
else if (!required && !repeated)
|
||||
out(file, " [{}]", arg.name);
|
||||
}
|
||||
outln(file);
|
||||
|
||||
if (m_general_help != nullptr && m_general_help[0] != '\0') {
|
||||
outln(file, "\nDescription:");
|
||||
outln(file, m_general_help);
|
||||
}
|
||||
|
||||
if (!m_options.is_empty())
|
||||
outln(file, "\nOptions:");
|
||||
for (auto& opt : m_options) {
|
||||
auto print_argument = [&]() {
|
||||
if (opt.value_name) {
|
||||
if (opt.requires_argument)
|
||||
out(file, " {}", opt.value_name);
|
||||
else
|
||||
out(file, " [{}]", opt.value_name);
|
||||
}
|
||||
};
|
||||
out(file, "\t");
|
||||
if (opt.short_name) {
|
||||
out(file, "\033[1m-{}\033[0m", opt.short_name);
|
||||
print_argument();
|
||||
}
|
||||
if (opt.short_name && opt.long_name)
|
||||
out(file, ", ");
|
||||
if (opt.long_name) {
|
||||
out(file, "\033[1m--{}\033[0m", opt.long_name);
|
||||
print_argument();
|
||||
}
|
||||
|
||||
if (opt.help_string)
|
||||
out(file, "\t{}", opt.help_string);
|
||||
outln(file);
|
||||
}
|
||||
|
||||
if (!m_positional_args.is_empty())
|
||||
outln(file, "\nArguments:");
|
||||
|
||||
for (auto& arg : m_positional_args) {
|
||||
out(file, "\t\033[1m{}\033[0m", arg.name);
|
||||
if (arg.help_string)
|
||||
out(file, "\t{}", arg.help_string);
|
||||
outln(file);
|
||||
}
|
||||
}
|
||||
|
||||
void ArgsParser::add_option(Option&& option)
|
||||
{
|
||||
m_options.append(move(option));
|
||||
}
|
||||
|
||||
void ArgsParser::add_option(bool& value, const char* help_string, const char* long_name, char short_name)
|
||||
{
|
||||
Option option {
|
||||
false,
|
||||
help_string,
|
||||
long_name,
|
||||
short_name,
|
||||
nullptr,
|
||||
[&value](const char* s) {
|
||||
ASSERT(s == nullptr);
|
||||
value = true;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
add_option(move(option));
|
||||
}
|
||||
|
||||
void ArgsParser::add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
|
||||
{
|
||||
Option option {
|
||||
true,
|
||||
help_string,
|
||||
long_name,
|
||||
short_name,
|
||||
value_name,
|
||||
[&value](const char* s) {
|
||||
value = s;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
add_option(move(option));
|
||||
}
|
||||
|
||||
void ArgsParser::add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
|
||||
{
|
||||
Option option {
|
||||
true,
|
||||
help_string,
|
||||
long_name,
|
||||
short_name,
|
||||
value_name,
|
||||
[&value](const char* s) {
|
||||
auto opt = StringView(s).to_int();
|
||||
value = opt.value_or(0);
|
||||
return opt.has_value();
|
||||
}
|
||||
};
|
||||
add_option(move(option));
|
||||
}
|
||||
|
||||
void ArgsParser::add_option(double& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
|
||||
{
|
||||
Option option {
|
||||
true,
|
||||
help_string,
|
||||
long_name,
|
||||
short_name,
|
||||
value_name,
|
||||
[&value](const char* s) {
|
||||
auto opt = convert_to_double(s);
|
||||
value = opt.value_or(0.0);
|
||||
return opt.has_value();
|
||||
}
|
||||
};
|
||||
add_option(move(option));
|
||||
}
|
||||
|
||||
void ArgsParser::add_positional_argument(Arg&& arg)
|
||||
{
|
||||
m_positional_args.append(move(arg));
|
||||
}
|
||||
|
||||
void ArgsParser::add_positional_argument(const char*& value, const char* help_string, const char* name, Required required)
|
||||
{
|
||||
Arg arg {
|
||||
help_string,
|
||||
name,
|
||||
required == Required::Yes ? 1 : 0,
|
||||
1,
|
||||
[&value](const char* s) {
|
||||
value = s;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
add_positional_argument(move(arg));
|
||||
}
|
||||
|
||||
void ArgsParser::add_positional_argument(int& value, const char* help_string, const char* name, Required required)
|
||||
{
|
||||
Arg arg {
|
||||
help_string,
|
||||
name,
|
||||
required == Required::Yes ? 1 : 0,
|
||||
1,
|
||||
[&value](const char* s) {
|
||||
auto opt = StringView(s).to_int();
|
||||
value = opt.value_or(0);
|
||||
return opt.has_value();
|
||||
}
|
||||
};
|
||||
add_positional_argument(move(arg));
|
||||
}
|
||||
|
||||
void ArgsParser::add_positional_argument(double& value, const char* help_string, const char* name, Required required)
|
||||
{
|
||||
Arg arg {
|
||||
help_string,
|
||||
name,
|
||||
required == Required::Yes ? 1 : 0,
|
||||
1,
|
||||
[&value](const char* s) {
|
||||
auto opt = convert_to_double(s);
|
||||
value = opt.value_or(0.0);
|
||||
return opt.has_value();
|
||||
}
|
||||
};
|
||||
add_positional_argument(move(arg));
|
||||
}
|
||||
|
||||
void ArgsParser::add_positional_argument(Vector<const char*>& values, const char* help_string, const char* name, Required required)
|
||||
{
|
||||
Arg arg {
|
||||
help_string,
|
||||
name,
|
||||
required == Required::Yes ? 1 : 0,
|
||||
INT_MAX,
|
||||
[&values](const char* s) {
|
||||
values.append(s);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
add_positional_argument(move(arg));
|
||||
}
|
||||
|
||||
}
|
94
Userland/Libraries/LibCore/ArgsParser.h
Normal file
94
Userland/Libraries/LibCore/ArgsParser.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ArgsParser {
|
||||
public:
|
||||
ArgsParser();
|
||||
|
||||
enum class Required {
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
struct Option {
|
||||
bool requires_argument { true };
|
||||
const char* help_string { nullptr };
|
||||
const char* long_name { nullptr };
|
||||
char short_name { 0 };
|
||||
const char* value_name { nullptr };
|
||||
Function<bool(const char*)> accept_value;
|
||||
|
||||
String name_for_display() const
|
||||
{
|
||||
if (long_name)
|
||||
return String::format("--%s", long_name);
|
||||
return String::format("-%c", short_name);
|
||||
}
|
||||
};
|
||||
|
||||
struct Arg {
|
||||
const char* help_string { nullptr };
|
||||
const char* name { nullptr };
|
||||
int min_values { 0 };
|
||||
int max_values { 1 };
|
||||
Function<bool(const char*)> accept_value;
|
||||
};
|
||||
|
||||
bool parse(int argc, char** argv, bool exit_on_failure = true);
|
||||
// *Without* trailing newline!
|
||||
void set_general_help(const char* help_string) { m_general_help = help_string; };
|
||||
void print_usage(FILE*, const char* argv0);
|
||||
|
||||
void add_option(Option&&);
|
||||
void add_option(bool& value, const char* help_string, const char* long_name, char short_name);
|
||||
void add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
|
||||
void add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
|
||||
void add_option(double& value, const char* help_string, const char* long_name, char short_name, const char* value_name);
|
||||
|
||||
void add_positional_argument(Arg&&);
|
||||
void add_positional_argument(const char*& value, const char* help_string, const char* name, Required required = Required::Yes);
|
||||
void add_positional_argument(int& value, const char* help_string, const char* name, Required required = Required::Yes);
|
||||
void add_positional_argument(double& value, const char* help_string, const char* name, Required required = Required::Yes);
|
||||
void add_positional_argument(Vector<const char*>& value, const char* help_string, const char* name, Required required = Required::Yes);
|
||||
|
||||
private:
|
||||
Vector<Option> m_options;
|
||||
Vector<Arg> m_positional_args;
|
||||
|
||||
bool m_show_help { false };
|
||||
const char* m_general_help { nullptr };
|
||||
};
|
||||
|
||||
}
|
37
Userland/Libraries/LibCore/CMakeLists.txt
Normal file
37
Userland/Libraries/LibCore/CMakeLists.txt
Normal file
|
@ -0,0 +1,37 @@
|
|||
set(SOURCES
|
||||
Account.cpp
|
||||
ArgsParser.cpp
|
||||
ConfigFile.cpp
|
||||
Command.cpp
|
||||
DateTime.cpp
|
||||
DirectoryWatcher.cpp
|
||||
DirIterator.cpp
|
||||
ElapsedTimer.cpp
|
||||
Event.cpp
|
||||
EventLoop.cpp
|
||||
File.cpp
|
||||
GetPassword.cpp
|
||||
Gzip.cpp
|
||||
IODevice.cpp
|
||||
LocalServer.cpp
|
||||
LocalSocket.cpp
|
||||
MimeData.cpp
|
||||
NetworkJob.cpp
|
||||
NetworkResponse.cpp
|
||||
Notifier.cpp
|
||||
Object.cpp
|
||||
ProcessStatisticsReader.cpp
|
||||
Property.cpp
|
||||
puff.cpp
|
||||
SocketAddress.cpp
|
||||
Socket.cpp
|
||||
StandardPaths.cpp
|
||||
TCPServer.cpp
|
||||
TCPSocket.cpp
|
||||
Timer.cpp
|
||||
UDPServer.cpp
|
||||
UDPSocket.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibCore core)
|
||||
target_link_libraries(LibCore LibC LibCrypt)
|
127
Userland/Libraries/LibCore/Command.cpp
Normal file
127
Userland/Libraries/LibCore/Command.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Command.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// #define DBG_FAILED_COMMANDS
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Only supported in serenity mode because we use `posix_spawn_file_actions_addchdir`
|
||||
#ifdef __serenity__
|
||||
|
||||
String command(const String& command_string, Optional<LexicalPath> chdir)
|
||||
{
|
||||
auto parts = command_string.split(' ');
|
||||
if (parts.is_empty())
|
||||
return {};
|
||||
auto program = parts[0];
|
||||
parts.remove(0);
|
||||
return command(program, parts, chdir);
|
||||
}
|
||||
|
||||
String command(const String& program, const Vector<String>& arguments, Optional<LexicalPath> chdir)
|
||||
{
|
||||
int stdout_pipe[2] = {};
|
||||
int stderr_pipe[2] = {};
|
||||
if (pipe2(stdout_pipe, O_CLOEXEC)) {
|
||||
perror("pipe2");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
if (pipe2(stderr_pipe, O_CLOEXEC)) {
|
||||
perror("pipe2");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto close_pipes = ScopeGuard([stderr_pipe, stdout_pipe] {
|
||||
// The write-ends of these pipes are closed manually
|
||||
close(stdout_pipe[0]);
|
||||
close(stderr_pipe[0]);
|
||||
});
|
||||
|
||||
Vector<const char*> parts = { program.characters() };
|
||||
for (const auto& part : arguments) {
|
||||
parts.append(part.characters());
|
||||
}
|
||||
parts.append(nullptr);
|
||||
|
||||
const char** argv = parts.data();
|
||||
|
||||
posix_spawn_file_actions_t action;
|
||||
posix_spawn_file_actions_init(&action);
|
||||
if (chdir.has_value()) {
|
||||
posix_spawn_file_actions_addchdir(&action, chdir.value().string().characters());
|
||||
}
|
||||
posix_spawn_file_actions_adddup2(&action, stdout_pipe[1], STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&action, stderr_pipe[1], STDERR_FILENO);
|
||||
|
||||
pid_t pid;
|
||||
if ((errno = posix_spawnp(&pid, program.characters(), &action, nullptr, const_cast<char**>(argv), environ))) {
|
||||
perror("posix_spawn");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
int wstatus;
|
||||
waitpid(pid, &wstatus, 0);
|
||||
posix_spawn_file_actions_destroy(&action);
|
||||
|
||||
// close the write-ends so reading wouldn't block
|
||||
close(stdout_pipe[1]);
|
||||
close(stderr_pipe[1]);
|
||||
|
||||
auto read_all_from_pipe = [](int pipe[2]) {
|
||||
auto result_file = Core::File::construct();
|
||||
if (!result_file->open(pipe[0], Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) {
|
||||
perror("open");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
return String::copy(result_file->read_all());
|
||||
};
|
||||
|
||||
if (WEXITSTATUS(wstatus) != 0) {
|
||||
# ifdef DBG_FAILED_COMMANDS
|
||||
dbgln("command failed. stderr: {}", read_all_from_pipe(stderr_pipe));
|
||||
# endif
|
||||
return {};
|
||||
}
|
||||
|
||||
auto result = read_all_from_pipe(stdout_pipe);
|
||||
if (result.is_null())
|
||||
return "";
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
40
Userland/Libraries/LibCore/Command.h
Normal file
40
Userland/Libraries/LibCore/Command.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <spawn.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
// If the executed command fails, the returned String will be in the null state.
|
||||
String command(const String& program, const Vector<String>& arguments, Optional<LexicalPath> chdir);
|
||||
String command(const String& command_string, Optional<LexicalPath> chdir);
|
||||
|
||||
}
|
242
Userland/Libraries/LibCore/ConfigFile.cpp
Normal file
242
Userland/Libraries/LibCore/ConfigFile.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
NonnullRefPtr<ConfigFile> ConfigFile::get_for_lib(const String& lib_name)
|
||||
{
|
||||
String directory = StandardPaths::config_directory();
|
||||
auto path = String::formatted("{}/lib/{}.ini", directory, lib_name);
|
||||
|
||||
return adopt(*new ConfigFile(path));
|
||||
}
|
||||
|
||||
NonnullRefPtr<ConfigFile> ConfigFile::get_for_app(const String& app_name)
|
||||
{
|
||||
String directory = StandardPaths::config_directory();
|
||||
auto path = String::formatted("{}/{}.ini", directory, app_name);
|
||||
return adopt(*new ConfigFile(path));
|
||||
}
|
||||
|
||||
NonnullRefPtr<ConfigFile> ConfigFile::get_for_system(const String& app_name)
|
||||
{
|
||||
auto path = String::formatted("/etc/{}.ini", app_name);
|
||||
return adopt(*new ConfigFile(path));
|
||||
}
|
||||
|
||||
NonnullRefPtr<ConfigFile> ConfigFile::open(const String& path)
|
||||
{
|
||||
return adopt(*new ConfigFile(path));
|
||||
}
|
||||
|
||||
ConfigFile::ConfigFile(const String& file_name)
|
||||
: m_file_name(file_name)
|
||||
{
|
||||
reparse();
|
||||
}
|
||||
|
||||
ConfigFile::~ConfigFile()
|
||||
{
|
||||
sync();
|
||||
}
|
||||
|
||||
void ConfigFile::reparse()
|
||||
{
|
||||
m_groups.clear();
|
||||
|
||||
auto file = File::construct(m_file_name);
|
||||
if (!file->open(IODevice::OpenMode::ReadOnly))
|
||||
return;
|
||||
|
||||
HashMap<String, String>* current_group = nullptr;
|
||||
|
||||
while (file->can_read_line()) {
|
||||
auto line = file->read_line();
|
||||
auto* cp = line.characters();
|
||||
|
||||
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 ConfigFile::read_entry(const String& group, const String& key, const String& default_value) const
|
||||
{
|
||||
if (!has_key(group, key)) {
|
||||
return default_value;
|
||||
}
|
||||
auto it = m_groups.find(group);
|
||||
auto jt = it->value.find(key);
|
||||
return jt->value;
|
||||
}
|
||||
|
||||
int ConfigFile::read_num_entry(const String& group, const String& key, int default_value) const
|
||||
{
|
||||
if (!has_key(group, key)) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return read_entry(group, key).to_int().value_or(default_value);
|
||||
}
|
||||
|
||||
bool ConfigFile::read_bool_entry(const String& group, const String& key, bool default_value) const
|
||||
{
|
||||
auto value = read_entry(group, key, default_value ? "1" : "0");
|
||||
if (value == "1" || value.to_lowercase() == "true")
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConfigFile::write_entry(const String& group, const String& key, const String& value)
|
||||
{
|
||||
m_groups.ensure(group).ensure(key) = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void ConfigFile::write_num_entry(const String& group, const String& key, int value)
|
||||
{
|
||||
write_entry(group, key, String::number(value));
|
||||
}
|
||||
void ConfigFile::write_bool_entry(const String& group, const String& key, bool value)
|
||||
{
|
||||
write_entry(group, key, value ? "1" : "0");
|
||||
}
|
||||
void ConfigFile::write_color_entry(const String& group, const String& key, Color value)
|
||||
{
|
||||
write_entry(group, key, String::formatted("{},{},{},{}", value.red(), value.green(), value.blue(), value.alpha()));
|
||||
}
|
||||
|
||||
bool ConfigFile::sync()
|
||||
{
|
||||
if (!m_dirty)
|
||||
return true;
|
||||
|
||||
FILE* fp = fopen(m_file_name.characters(), "wb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
for (auto& it : m_groups) {
|
||||
outln(fp, "[{}]", it.key);
|
||||
for (auto& jt : it.value)
|
||||
outln(fp, "{}={}", jt.key, jt.value);
|
||||
outln(fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
m_dirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigFile::dump() const
|
||||
{
|
||||
for (auto& it : m_groups) {
|
||||
outln("[{}]", it.key);
|
||||
for (auto& jt : it.value)
|
||||
outln("{}={}", jt.key, jt.value);
|
||||
outln();
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> ConfigFile::groups() const
|
||||
{
|
||||
return m_groups.keys();
|
||||
}
|
||||
|
||||
Vector<String> ConfigFile::keys(const String& group) const
|
||||
{
|
||||
auto it = m_groups.find(group);
|
||||
if (it == m_groups.end())
|
||||
return {};
|
||||
return it->value.keys();
|
||||
}
|
||||
|
||||
bool ConfigFile::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 ConfigFile::has_group(const String& group) const
|
||||
{
|
||||
return m_groups.contains(group);
|
||||
}
|
||||
|
||||
void ConfigFile::remove_group(const String& group)
|
||||
{
|
||||
m_groups.remove(group);
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void ConfigFile::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;
|
||||
}
|
||||
|
||||
}
|
82
Userland/Libraries/LibCore/ConfigFile.h
Normal file
82
Userland/Libraries/LibCore/ConfigFile.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Color.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ConfigFile : public RefCounted<ConfigFile> {
|
||||
public:
|
||||
static NonnullRefPtr<ConfigFile> get_for_lib(const String& lib_name);
|
||||
static NonnullRefPtr<ConfigFile> get_for_app(const String& app_name);
|
||||
static NonnullRefPtr<ConfigFile> get_for_system(const String& app_name);
|
||||
static NonnullRefPtr<ConfigFile> open(const String& path);
|
||||
~ConfigFile();
|
||||
|
||||
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_value = 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;
|
||||
|
||||
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 ConfigFile(const String& file_name);
|
||||
|
||||
void reparse();
|
||||
|
||||
String m_file_name;
|
||||
HashMap<String, HashMap<String, String>> m_groups;
|
||||
bool m_dirty { false };
|
||||
};
|
||||
|
||||
}
|
263
Userland/Libraries/LibCore/DateTime.cpp
Normal file
263
Userland/Libraries/LibCore/DateTime.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/DateTime.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
DateTime DateTime::now()
|
||||
{
|
||||
return from_timestamp(time(nullptr));
|
||||
}
|
||||
|
||||
DateTime DateTime::create(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
|
||||
{
|
||||
DateTime dt;
|
||||
dt.set_time(year, month, day, hour, minute, second);
|
||||
return dt;
|
||||
}
|
||||
|
||||
DateTime DateTime::from_timestamp(time_t timestamp)
|
||||
{
|
||||
struct tm tm;
|
||||
localtime_r(×tamp, &tm);
|
||||
DateTime dt;
|
||||
dt.m_year = tm.tm_year + 1900;
|
||||
dt.m_month = tm.tm_mon + 1;
|
||||
dt.m_day = tm.tm_mday;
|
||||
dt.m_hour = tm.tm_hour;
|
||||
dt.m_minute = tm.tm_min;
|
||||
dt.m_second = tm.tm_sec;
|
||||
dt.m_timestamp = timestamp;
|
||||
return dt;
|
||||
}
|
||||
|
||||
unsigned DateTime::weekday() const
|
||||
{
|
||||
return ::day_of_week(m_year, m_month, m_day);
|
||||
}
|
||||
|
||||
unsigned DateTime::days_in_month() const
|
||||
{
|
||||
return ::days_in_month(m_year, m_month);
|
||||
}
|
||||
|
||||
unsigned DateTime::day_of_year() const
|
||||
{
|
||||
return ::day_of_year(m_year, m_month, m_day);
|
||||
}
|
||||
|
||||
bool DateTime::is_leap_year() const
|
||||
{
|
||||
return ::is_leap_year(m_year);
|
||||
}
|
||||
|
||||
void DateTime::set_time(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
|
||||
{
|
||||
struct tm tm = {};
|
||||
tm.tm_sec = (int)second;
|
||||
tm.tm_min = (int)minute;
|
||||
tm.tm_hour = (int)hour;
|
||||
tm.tm_mday = (int)day;
|
||||
tm.tm_mon = (int)month - 1;
|
||||
tm.tm_year = (int)year - 1900;
|
||||
tm.tm_isdst = -1;
|
||||
// mktime() doesn't read tm.tm_wday and tm.tm_yday, no need to fill them in.
|
||||
|
||||
m_timestamp = mktime(&tm);
|
||||
|
||||
// mktime() normalizes the components to the right ranges (Jan 32 -> Feb 1 etc), so read fields back out from tm.
|
||||
m_year = tm.tm_year + 1900;
|
||||
m_month = tm.tm_mon + 1;
|
||||
m_day = tm.tm_mday;
|
||||
m_hour = tm.tm_hour;
|
||||
m_minute = tm.tm_min;
|
||||
m_second = tm.tm_sec;
|
||||
}
|
||||
|
||||
String DateTime::to_string(const String& format) const
|
||||
{
|
||||
|
||||
const char wday_short_names[7][4] = {
|
||||
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
||||
};
|
||||
const char wday_long_names[7][10] = {
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||
};
|
||||
const char mon_short_names[12][4] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
const char mon_long_names[12][10] = {
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
};
|
||||
|
||||
struct tm tm;
|
||||
localtime_r(&m_timestamp, &tm);
|
||||
StringBuilder builder;
|
||||
const int format_len = format.length();
|
||||
|
||||
for (int i = 0; i < format_len; ++i) {
|
||||
if (format[i] != '%') {
|
||||
builder.append(format[i]);
|
||||
} else {
|
||||
if (++i == format_len)
|
||||
return String();
|
||||
|
||||
switch (format[i]) {
|
||||
case 'a':
|
||||
builder.append(wday_short_names[tm.tm_wday]);
|
||||
break;
|
||||
case 'A':
|
||||
builder.append(wday_long_names[tm.tm_wday]);
|
||||
break;
|
||||
case 'b':
|
||||
builder.append(mon_short_names[tm.tm_mon]);
|
||||
break;
|
||||
case 'B':
|
||||
builder.append(mon_long_names[tm.tm_mon]);
|
||||
break;
|
||||
case 'C':
|
||||
builder.appendf("%02d", (tm.tm_year + 1900) / 100);
|
||||
break;
|
||||
case 'd':
|
||||
builder.appendf("%02d", tm.tm_mday);
|
||||
break;
|
||||
case 'D':
|
||||
builder.appendf("%02d/%02d/%02d", tm.tm_mon + 1, tm.tm_mday, (tm.tm_year + 1900) % 100);
|
||||
break;
|
||||
case 'e':
|
||||
builder.appendf("%2d", tm.tm_mday);
|
||||
break;
|
||||
case 'h':
|
||||
builder.append(mon_short_names[tm.tm_mon]);
|
||||
break;
|
||||
case 'H':
|
||||
builder.appendf("%02d", tm.tm_hour);
|
||||
break;
|
||||
case 'I':
|
||||
builder.appendf("%02d", tm.tm_hour % 12);
|
||||
break;
|
||||
case 'j':
|
||||
builder.appendf("%03d", tm.tm_yday + 1);
|
||||
break;
|
||||
case 'm':
|
||||
builder.appendf("%02d", tm.tm_mon + 1);
|
||||
break;
|
||||
case 'M':
|
||||
builder.appendf("%02d", tm.tm_min);
|
||||
break;
|
||||
case 'n':
|
||||
builder.append('\n');
|
||||
break;
|
||||
case 'p':
|
||||
builder.append(tm.tm_hour < 12 ? "a.m." : "p.m.");
|
||||
break;
|
||||
case 'r':
|
||||
builder.appendf("%02d:%02d:%02d %s", tm.tm_hour % 12, tm.tm_min, tm.tm_sec, tm.tm_hour < 12 ? "a.m." : "p.m.");
|
||||
break;
|
||||
case 'R':
|
||||
builder.appendf("%02d:%02d", tm.tm_hour, tm.tm_min);
|
||||
break;
|
||||
case 'S':
|
||||
builder.appendf("%02d", tm.tm_sec);
|
||||
break;
|
||||
case 't':
|
||||
builder.append('\t');
|
||||
break;
|
||||
case 'T':
|
||||
builder.appendf("%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
break;
|
||||
case 'u':
|
||||
builder.appendf("%d", tm.tm_wday ? tm.tm_wday : 7);
|
||||
break;
|
||||
case 'U': {
|
||||
const int wday_of_year_beginning = (tm.tm_wday + 6 * tm.tm_yday) % 7;
|
||||
const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
|
||||
builder.appendf("%02d", week_number);
|
||||
break;
|
||||
}
|
||||
case 'V': {
|
||||
const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
|
||||
int week_number = (tm.tm_yday + wday_of_year_beginning) / 7 + 1;
|
||||
if (wday_of_year_beginning > 3) {
|
||||
if (tm.tm_yday >= 7 - wday_of_year_beginning)
|
||||
--week_number;
|
||||
else {
|
||||
const int days_of_last_year = days_in_year(tm.tm_year + 1900 - 1);
|
||||
const int wday_of_last_year_beginning = (wday_of_year_beginning + 6 * days_of_last_year) % 7;
|
||||
week_number = (days_of_last_year + wday_of_last_year_beginning) / 7 + 1;
|
||||
if (wday_of_last_year_beginning > 3)
|
||||
--week_number;
|
||||
}
|
||||
}
|
||||
builder.appendf("%02d", week_number);
|
||||
break;
|
||||
}
|
||||
case 'w':
|
||||
builder.appendf("%d", tm.tm_wday);
|
||||
break;
|
||||
case 'W': {
|
||||
const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
|
||||
const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
|
||||
builder.appendf("%02d", week_number);
|
||||
break;
|
||||
}
|
||||
case 'y':
|
||||
builder.appendf("%02d", (tm.tm_year + 1900) % 100);
|
||||
break;
|
||||
case 'Y':
|
||||
builder.appendf("%d", tm.tm_year + 1900);
|
||||
break;
|
||||
case '%':
|
||||
builder.append('%');
|
||||
break;
|
||||
default:
|
||||
return String();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool DateTime::is_before(const String& other) const
|
||||
{
|
||||
auto now_string = String::formatted("{:04}{:02}{:02}{:02}{:02}{:02}Z", year(), month(), weekday(), hour(), minute(), second());
|
||||
return __builtin_strcasecmp(now_string.characters(), other.characters()) < 0;
|
||||
}
|
||||
|
||||
const LogStream& operator<<(const LogStream& stream, const DateTime& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
74
Userland/Libraries/LibCore/DateTime.h
Normal file
74
Userland/Libraries/LibCore/DateTime.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Represents a time in local time.
|
||||
class DateTime {
|
||||
public:
|
||||
time_t timestamp() const { return m_timestamp; }
|
||||
|
||||
unsigned year() const { return m_year; }
|
||||
unsigned month() const { return m_month; }
|
||||
unsigned day() const { return m_day; }
|
||||
|
||||
unsigned hour() const { return m_hour; }
|
||||
unsigned minute() const { return m_minute; }
|
||||
unsigned second() const { return m_second; }
|
||||
unsigned weekday() const;
|
||||
unsigned days_in_month() const;
|
||||
unsigned day_of_year() const;
|
||||
bool is_leap_year() const;
|
||||
|
||||
void set_time(unsigned year, unsigned month = 1, unsigned day = 0, unsigned hour = 0, unsigned minute = 0, unsigned second = 0);
|
||||
String to_string(const String& format = "%Y-%m-%d %H:%M:%S") const;
|
||||
|
||||
static DateTime create(unsigned year, unsigned month = 1, unsigned day = 0, unsigned hour = 0, unsigned minute = 0, unsigned second = 0);
|
||||
static DateTime now();
|
||||
static DateTime from_timestamp(time_t);
|
||||
|
||||
// FIXME: This should be replaced with a proper comparison
|
||||
// operator when we get the equivalent of strptime
|
||||
bool is_before(const String&) const;
|
||||
|
||||
private:
|
||||
time_t m_timestamp { 0 };
|
||||
unsigned m_year { 0 };
|
||||
unsigned m_month { 0 };
|
||||
unsigned m_day { 0 };
|
||||
unsigned m_hour { 0 };
|
||||
unsigned m_minute { 0 };
|
||||
unsigned m_second { 0 };
|
||||
};
|
||||
|
||||
const LogStream& operator<<(const LogStream&, const DateTime&);
|
||||
|
||||
}
|
121
Userland/Libraries/LibCore/DirIterator.cpp
Normal file
121
Userland/Libraries/LibCore/DirIterator.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/DirIterator.h>
|
||||
#include <errno.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
DirIterator::DirIterator(const StringView& path, Flags flags)
|
||||
: m_path(path)
|
||||
, m_flags(flags)
|
||||
{
|
||||
m_dir = opendir(path.to_string().characters());
|
||||
if (!m_dir) {
|
||||
m_error = errno;
|
||||
}
|
||||
}
|
||||
|
||||
DirIterator::~DirIterator()
|
||||
{
|
||||
if (m_dir) {
|
||||
closedir(m_dir);
|
||||
m_dir = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool DirIterator::advance_next()
|
||||
{
|
||||
if (!m_dir)
|
||||
return false;
|
||||
|
||||
while (true) {
|
||||
errno = 0;
|
||||
auto* de = readdir(m_dir);
|
||||
if (!de) {
|
||||
m_error = errno;
|
||||
m_next = String();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_next = de->d_name;
|
||||
if (m_next.is_null())
|
||||
return false;
|
||||
|
||||
if (m_flags & Flags::SkipDots && m_next.starts_with('.'))
|
||||
continue;
|
||||
|
||||
if (m_flags & Flags::SkipParentAndBaseDir && (m_next == "." || m_next == ".."))
|
||||
continue;
|
||||
|
||||
return !m_next.is_empty();
|
||||
}
|
||||
}
|
||||
|
||||
bool DirIterator::has_next()
|
||||
{
|
||||
if (!m_next.is_null())
|
||||
return true;
|
||||
|
||||
return advance_next();
|
||||
}
|
||||
|
||||
String DirIterator::next_path()
|
||||
{
|
||||
if (m_next.is_null())
|
||||
advance_next();
|
||||
|
||||
auto tmp = m_next;
|
||||
m_next = String();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
String DirIterator::next_full_path()
|
||||
{
|
||||
return String::formatted("{}/{}", m_path, next_path());
|
||||
}
|
||||
|
||||
String find_executable_in_path(String filename)
|
||||
{
|
||||
if (filename.starts_with('/')) {
|
||||
if (access(filename.characters(), X_OK) == 0)
|
||||
return filename;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
for (auto directory : String { getenv("PATH") }.split(':')) {
|
||||
auto fullpath = String::formatted("{}/{}", directory, filename);
|
||||
|
||||
if (access(fullpath.characters(), X_OK) == 0)
|
||||
return fullpath;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
65
Userland/Libraries/LibCore/DirIterator.h
Normal file
65
Userland/Libraries/LibCore/DirIterator.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DirIterator {
|
||||
public:
|
||||
enum Flags {
|
||||
NoFlags = 0x0,
|
||||
SkipDots = 0x1,
|
||||
SkipParentAndBaseDir = 0x2,
|
||||
};
|
||||
|
||||
DirIterator(const StringView& path, Flags = Flags::NoFlags);
|
||||
~DirIterator();
|
||||
|
||||
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();
|
||||
String next_full_path();
|
||||
|
||||
private:
|
||||
DIR* m_dir = nullptr;
|
||||
int m_error = 0;
|
||||
String m_next;
|
||||
String m_path;
|
||||
int m_flags;
|
||||
|
||||
bool advance_next();
|
||||
};
|
||||
|
||||
String find_executable_in_path(String filename);
|
||||
|
||||
}
|
98
Userland/Libraries/LibCore/DirectoryWatcher.cpp
Normal file
98
Userland/Libraries/LibCore/DirectoryWatcher.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "DirectoryWatcher.h"
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCore/DirIterator.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Only supported in serenity mode because we use `watch_file`
|
||||
#ifdef __serenity__
|
||||
|
||||
DirectoryWatcher::DirectoryWatcher(const String& path)
|
||||
: m_path(path)
|
||||
{
|
||||
m_watcher_fd = watch_file(path.characters(), path.length());
|
||||
ASSERT(m_watcher_fd != -1);
|
||||
}
|
||||
|
||||
DirectoryWatcher::~DirectoryWatcher()
|
||||
{
|
||||
close(m_watcher_fd);
|
||||
}
|
||||
|
||||
Optional<DirectoryWatcher::Event> DirectoryWatcher::wait_for_event()
|
||||
{
|
||||
InodeWatcherEvent event {};
|
||||
int rc = read(m_watcher_fd, &event, sizeof(event));
|
||||
if (rc <= 0)
|
||||
return {};
|
||||
|
||||
Event result;
|
||||
if (event.type == InodeWatcherEvent::Type::ChildAdded)
|
||||
result.type = Event::Type::ChildAdded;
|
||||
else if (event.type == InodeWatcherEvent::Type::ChildRemoved)
|
||||
result.type = Event::Type::ChildRemoved;
|
||||
else
|
||||
return {};
|
||||
|
||||
auto child_path = get_child_with_inode_index(event.inode_index);
|
||||
if (!LexicalPath(child_path).is_valid())
|
||||
return {};
|
||||
|
||||
result.child_path = child_path;
|
||||
return result;
|
||||
}
|
||||
|
||||
String DirectoryWatcher::get_child_with_inode_index(unsigned child_inode_index) const
|
||||
{
|
||||
DirIterator iterator(m_path, Core::DirIterator::SkipDots);
|
||||
if (iterator.has_error()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
while (iterator.has_next()) {
|
||||
auto child_full_path = String::formatted("{}/{}", m_path, iterator.next_path());
|
||||
struct stat st;
|
||||
|
||||
if (lstat(child_full_path.characters(), &st)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (st.st_ino == child_inode_index) {
|
||||
return child_full_path;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
61
Userland/Libraries/LibCore/DirectoryWatcher.h
Normal file
61
Userland/Libraries/LibCore/DirectoryWatcher.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/String.h>
|
||||
#include <Kernel/API/InodeWatcherEvent.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DirectoryWatcher {
|
||||
AK_MAKE_NONCOPYABLE(DirectoryWatcher);
|
||||
|
||||
public:
|
||||
explicit DirectoryWatcher(const String& path);
|
||||
~DirectoryWatcher();
|
||||
|
||||
struct Event {
|
||||
enum class Type {
|
||||
ChildAdded,
|
||||
ChildRemoved,
|
||||
};
|
||||
Type type;
|
||||
String child_path;
|
||||
};
|
||||
|
||||
Optional<Event> wait_for_event();
|
||||
|
||||
private:
|
||||
String get_child_with_inode_index(unsigned) const;
|
||||
|
||||
String m_path;
|
||||
int m_watcher_fd { -1 };
|
||||
};
|
||||
|
||||
}
|
57
Userland/Libraries/LibCore/ElapsedTimer.cpp
Normal file
57
Userland/Libraries/LibCore/ElapsedTimer.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/ElapsedTimer.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
void ElapsedTimer::start()
|
||||
{
|
||||
m_valid = true;
|
||||
timespec now_spec;
|
||||
clock_gettime(m_precise ? CLOCK_MONOTONIC : CLOCK_MONOTONIC_COARSE, &now_spec);
|
||||
m_origin_time.tv_sec = now_spec.tv_sec;
|
||||
m_origin_time.tv_usec = now_spec.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
int ElapsedTimer::elapsed() const
|
||||
{
|
||||
ASSERT(is_valid());
|
||||
struct timeval now;
|
||||
timespec now_spec;
|
||||
clock_gettime(m_precise ? CLOCK_MONOTONIC : CLOCK_MONOTONIC_COARSE, &now_spec);
|
||||
now.tv_sec = now_spec.tv_sec;
|
||||
now.tv_usec = now_spec.tv_nsec / 1000;
|
||||
struct timeval diff;
|
||||
timeval_sub(now, m_origin_time, diff);
|
||||
return diff.tv_sec * 1000 + diff.tv_usec / 1000;
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibCore/ElapsedTimer.h
Normal file
54
Userland/Libraries/LibCore/ElapsedTimer.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ElapsedTimer {
|
||||
public:
|
||||
ElapsedTimer(bool precise = false)
|
||||
: m_precise(precise)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_valid() const { return m_valid; }
|
||||
void start();
|
||||
int elapsed() const;
|
||||
|
||||
const struct timeval& origin_time() const { return m_origin_time; }
|
||||
|
||||
private:
|
||||
bool m_precise { false };
|
||||
bool m_valid { false };
|
||||
struct timeval m_origin_time {
|
||||
0, 0
|
||||
};
|
||||
};
|
||||
|
||||
}
|
72
Userland/Libraries/LibCore/Event.cpp
Normal file
72
Userland/Libraries/LibCore/Event.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
ChildEvent::ChildEvent(Type type, Object& child, Object* insertion_before_child)
|
||||
: Core::Event(type)
|
||||
, m_child(child.make_weak_ptr())
|
||||
, m_insertion_before_child(AK::try_make_weak_ptr(insertion_before_child))
|
||||
{
|
||||
}
|
||||
|
||||
ChildEvent::~ChildEvent()
|
||||
{
|
||||
}
|
||||
|
||||
Object* ChildEvent::child()
|
||||
{
|
||||
if (auto ref = m_child.strong_ref())
|
||||
return ref.ptr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Object* ChildEvent::child() const
|
||||
{
|
||||
if (auto ref = m_child.strong_ref())
|
||||
return ref.ptr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Object* ChildEvent::insertion_before_child()
|
||||
{
|
||||
if (auto ref = m_insertion_before_child.strong_ref())
|
||||
return ref.ptr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Object* ChildEvent::insertion_before_child() const
|
||||
{
|
||||
if (auto ref = m_insertion_before_child.strong_ref())
|
||||
return ref.ptr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
159
Userland/Libraries/LibCore/Event.h
Normal file
159
Userland/Libraries/LibCore/Event.h
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Forward.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Event {
|
||||
public:
|
||||
enum Type {
|
||||
Invalid = 0,
|
||||
Quit,
|
||||
Timer,
|
||||
NotifierRead,
|
||||
NotifierWrite,
|
||||
DeferredInvoke,
|
||||
ChildAdded,
|
||||
ChildRemoved,
|
||||
Custom,
|
||||
};
|
||||
|
||||
Event() { }
|
||||
explicit Event(unsigned type)
|
||||
: m_type(type)
|
||||
{
|
||||
}
|
||||
virtual ~Event() { }
|
||||
|
||||
unsigned type() const { return m_type; }
|
||||
|
||||
bool is_accepted() const { return m_accepted; }
|
||||
void accept() { m_accepted = true; }
|
||||
void ignore() { m_accepted = false; }
|
||||
|
||||
private:
|
||||
unsigned m_type { Type::Invalid };
|
||||
bool m_accepted { true };
|
||||
};
|
||||
|
||||
class DeferredInvocationEvent : public Event {
|
||||
friend class EventLoop;
|
||||
|
||||
public:
|
||||
DeferredInvocationEvent(Function<void(Object&)> invokee)
|
||||
: Event(Event::Type::DeferredInvoke)
|
||||
, m_invokee(move(invokee))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Function<void(Object&)> m_invokee;
|
||||
};
|
||||
|
||||
class TimerEvent final : public Event {
|
||||
public:
|
||||
explicit TimerEvent(int timer_id)
|
||||
: Event(Event::Timer)
|
||||
, m_timer_id(timer_id)
|
||||
{
|
||||
}
|
||||
~TimerEvent() { }
|
||||
|
||||
int timer_id() const { return m_timer_id; }
|
||||
|
||||
private:
|
||||
int m_timer_id;
|
||||
};
|
||||
|
||||
class NotifierReadEvent final : public Event {
|
||||
public:
|
||||
explicit NotifierReadEvent(int fd)
|
||||
: Event(Event::NotifierRead)
|
||||
, m_fd(fd)
|
||||
{
|
||||
}
|
||||
~NotifierReadEvent() { }
|
||||
|
||||
int fd() const { return m_fd; }
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
|
||||
class NotifierWriteEvent final : public Event {
|
||||
public:
|
||||
explicit NotifierWriteEvent(int fd)
|
||||
: Event(Event::NotifierWrite)
|
||||
, m_fd(fd)
|
||||
{
|
||||
}
|
||||
~NotifierWriteEvent() { }
|
||||
|
||||
int fd() const { return m_fd; }
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
|
||||
class ChildEvent final : public Event {
|
||||
public:
|
||||
ChildEvent(Type, Object& child, Object* insertion_before_child = nullptr);
|
||||
~ChildEvent();
|
||||
|
||||
Object* child();
|
||||
const Object* child() const;
|
||||
|
||||
Object* insertion_before_child();
|
||||
const Object* insertion_before_child() const;
|
||||
|
||||
private:
|
||||
WeakPtr<Object> m_child;
|
||||
WeakPtr<Object> m_insertion_before_child;
|
||||
};
|
||||
|
||||
class CustomEvent : public Event {
|
||||
public:
|
||||
CustomEvent(int custom_type)
|
||||
: Event(Event::Type::Custom)
|
||||
, m_custom_type(custom_type)
|
||||
{
|
||||
}
|
||||
~CustomEvent() { }
|
||||
|
||||
int custom_type() const { return m_custom_type; }
|
||||
|
||||
private:
|
||||
int m_custom_type { 0 };
|
||||
};
|
||||
|
||||
}
|
835
Userland/Libraries/LibCore/EventLoop.cpp
Normal file
835
Userland/Libraries/LibCore/EventLoop.cpp
Normal file
|
@ -0,0 +1,835 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/IDAllocator.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/Singleton.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/LocalSocket.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibCore/SyscallUtils.h>
|
||||
#include <LibThread/Lock.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//#define EVENTLOOP_DEBUG
|
||||
//#define DEFERRED_INVOKE_DEBUG
|
||||
|
||||
namespace Core {
|
||||
|
||||
class RPCClient;
|
||||
|
||||
struct EventLoopTimer {
|
||||
int timer_id { 0 };
|
||||
int interval { 0 };
|
||||
timeval fire_time { 0, 0 };
|
||||
bool should_reload { false };
|
||||
TimerShouldFireWhenNotVisible fire_when_not_visible { TimerShouldFireWhenNotVisible::No };
|
||||
WeakPtr<Object> owner;
|
||||
|
||||
void reload(const timeval& now);
|
||||
bool has_expired(const timeval& now) const;
|
||||
};
|
||||
|
||||
struct EventLoop::Private {
|
||||
LibThread::Lock lock;
|
||||
};
|
||||
|
||||
static EventLoop* s_main_event_loop;
|
||||
static Vector<EventLoop*>* s_event_loop_stack;
|
||||
static NeverDestroyed<IDAllocator> s_id_allocator;
|
||||
static HashMap<int, NonnullOwnPtr<EventLoopTimer>>* s_timers;
|
||||
static HashTable<Notifier*>* s_notifiers;
|
||||
int EventLoop::s_wake_pipe_fds[2];
|
||||
static RefPtr<LocalServer> s_rpc_server;
|
||||
HashMap<int, RefPtr<RPCClient>> s_rpc_clients;
|
||||
|
||||
class SignalHandlers : public RefCounted<SignalHandlers> {
|
||||
AK_MAKE_NONCOPYABLE(SignalHandlers);
|
||||
AK_MAKE_NONMOVABLE(SignalHandlers);
|
||||
|
||||
public:
|
||||
SignalHandlers(int signo, void (*handle_signal)(int));
|
||||
~SignalHandlers();
|
||||
|
||||
void dispatch();
|
||||
int add(Function<void(int)>&& handler);
|
||||
bool remove(int handler_id);
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
for (auto& handler : m_handlers_pending) {
|
||||
if (handler.value)
|
||||
return false; // an add is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.is_empty();
|
||||
}
|
||||
|
||||
bool have(int handler_id) const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // a deletion is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.contains(handler_id);
|
||||
}
|
||||
|
||||
int m_signo;
|
||||
void (*m_original_handler)(int); // TODO: can't use sighandler_t?
|
||||
HashMap<int, Function<void(int)>> m_handlers;
|
||||
HashMap<int, Function<void(int)>> m_handlers_pending;
|
||||
bool m_calling_handlers { false };
|
||||
};
|
||||
|
||||
struct SignalHandlersInfo {
|
||||
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
|
||||
int next_signal_id { 0 };
|
||||
};
|
||||
|
||||
template<bool create_if_null = true>
|
||||
inline SignalHandlersInfo* signals_info()
|
||||
{
|
||||
static SignalHandlersInfo* s_signals;
|
||||
return AK::Singleton<SignalHandlersInfo>::get(s_signals);
|
||||
}
|
||||
|
||||
pid_t EventLoop::s_pid;
|
||||
|
||||
class RPCClient : public Object {
|
||||
C_OBJECT(RPCClient)
|
||||
public:
|
||||
explicit RPCClient(RefPtr<LocalSocket> socket)
|
||||
: m_socket(move(socket))
|
||||
, m_client_id(s_id_allocator->allocate())
|
||||
{
|
||||
s_rpc_clients.set(m_client_id, this);
|
||||
add_child(*m_socket);
|
||||
m_socket->on_ready_to_read = [this] {
|
||||
u32 length;
|
||||
int nread = m_socket->read((u8*)&length, sizeof(length));
|
||||
if (nread == 0) {
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("RPC client disconnected");
|
||||
#endif
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
ASSERT(nread == sizeof(length));
|
||||
auto request = m_socket->read(length);
|
||||
|
||||
auto request_json = JsonValue::from_string(request);
|
||||
if (!request_json.has_value() || !request_json.value().is_object()) {
|
||||
dbgln("RPC client sent invalid request");
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
handle_request(request_json.value().as_object());
|
||||
};
|
||||
}
|
||||
virtual ~RPCClient() override
|
||||
{
|
||||
if (auto inspected_object = m_inspected_object.strong_ref())
|
||||
inspected_object->decrement_inspector_count({});
|
||||
}
|
||||
|
||||
void send_response(const JsonObject& response)
|
||||
{
|
||||
auto serialized = response.to_string();
|
||||
u32 length = serialized.length();
|
||||
m_socket->write((const u8*)&length, sizeof(length));
|
||||
m_socket->write(serialized);
|
||||
}
|
||||
|
||||
void handle_request(const JsonObject& request)
|
||||
{
|
||||
auto type = request.get("type").as_string_or({});
|
||||
|
||||
if (type.is_null()) {
|
||||
dbgln("RPC client sent request without type field");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "Identify") {
|
||||
JsonObject response;
|
||||
response.set("type", type);
|
||||
response.set("pid", getpid());
|
||||
#ifdef __serenity__
|
||||
char buffer[1024];
|
||||
if (get_process_name(buffer, sizeof(buffer)) >= 0) {
|
||||
response.set("process_name", buffer);
|
||||
} else {
|
||||
response.set("process_name", JsonValue());
|
||||
}
|
||||
#endif
|
||||
send_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "GetAllObjects") {
|
||||
JsonObject response;
|
||||
response.set("type", type);
|
||||
JsonArray objects;
|
||||
for (auto& object : Object::all_objects()) {
|
||||
JsonObject json_object;
|
||||
object.save_to(json_object);
|
||||
objects.append(move(json_object));
|
||||
}
|
||||
response.set("objects", move(objects));
|
||||
send_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "SetInspectedObject") {
|
||||
auto address = request.get("address").to_number<FlatPtr>();
|
||||
for (auto& object : Object::all_objects()) {
|
||||
if ((FlatPtr)&object == address) {
|
||||
if (auto inspected_object = m_inspected_object.strong_ref())
|
||||
inspected_object->decrement_inspector_count({});
|
||||
m_inspected_object = object;
|
||||
object.increment_inspector_count({});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "SetProperty") {
|
||||
auto address = request.get("address").to_number<FlatPtr>();
|
||||
for (auto& object : Object::all_objects()) {
|
||||
if ((FlatPtr)&object == address) {
|
||||
bool success = object.set_property(request.get("name").to_string(), request.get("value"));
|
||||
JsonObject response;
|
||||
response.set("type", "SetProperty");
|
||||
response.set("success", success);
|
||||
send_response(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "Disconnect") {
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
{
|
||||
s_rpc_clients.remove(m_client_id);
|
||||
s_id_allocator->deallocate(m_client_id);
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<LocalSocket> m_socket;
|
||||
WeakPtr<Object> m_inspected_object;
|
||||
int m_client_id { -1 };
|
||||
};
|
||||
|
||||
EventLoop::EventLoop()
|
||||
: m_private(make<Private>())
|
||||
{
|
||||
if (!s_event_loop_stack) {
|
||||
s_event_loop_stack = new Vector<EventLoop*>;
|
||||
s_timers = new HashMap<int, NonnullOwnPtr<EventLoopTimer>>;
|
||||
s_notifiers = new HashTable<Notifier*>;
|
||||
}
|
||||
|
||||
if (!s_main_event_loop) {
|
||||
s_main_event_loop = this;
|
||||
s_pid = getpid();
|
||||
#if defined(SOCK_NONBLOCK)
|
||||
int rc = pipe2(s_wake_pipe_fds, O_CLOEXEC);
|
||||
#else
|
||||
int rc = pipe(s_wake_pipe_fds);
|
||||
fcntl(s_wake_pipe_fds[0], F_SETFD, FD_CLOEXEC);
|
||||
fcntl(s_wake_pipe_fds[1], F_SETFD, FD_CLOEXEC);
|
||||
|
||||
#endif
|
||||
ASSERT(rc == 0);
|
||||
s_event_loop_stack->append(this);
|
||||
|
||||
if (!s_rpc_server) {
|
||||
if (!start_rpc_server())
|
||||
dbgln("Core::EventLoop: Failed to start an RPC server");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("{} Core::EventLoop constructed :)", getpid());
|
||||
#endif
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop()
|
||||
{
|
||||
}
|
||||
|
||||
bool EventLoop::start_rpc_server()
|
||||
{
|
||||
s_rpc_server = LocalServer::construct();
|
||||
s_rpc_server->set_name("Core::EventLoop_RPC_server");
|
||||
s_rpc_server->on_ready_to_accept = [&] {
|
||||
RPCClient::construct(s_rpc_server->accept());
|
||||
};
|
||||
return s_rpc_server->listen(String::formatted("/tmp/rpc/{}", getpid()));
|
||||
}
|
||||
|
||||
EventLoop& EventLoop::main()
|
||||
{
|
||||
ASSERT(s_main_event_loop);
|
||||
return *s_main_event_loop;
|
||||
}
|
||||
|
||||
EventLoop& EventLoop::current()
|
||||
{
|
||||
EventLoop* event_loop = s_event_loop_stack->last();
|
||||
ASSERT(event_loop != nullptr);
|
||||
return *event_loop;
|
||||
}
|
||||
|
||||
void EventLoop::quit(int code)
|
||||
{
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop::quit({})", code);
|
||||
#endif
|
||||
m_exit_requested = true;
|
||||
m_exit_code = code;
|
||||
}
|
||||
|
||||
void EventLoop::unquit()
|
||||
{
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop::unquit()");
|
||||
#endif
|
||||
m_exit_requested = false;
|
||||
m_exit_code = 0;
|
||||
}
|
||||
|
||||
struct EventLoopPusher {
|
||||
public:
|
||||
EventLoopPusher(EventLoop& event_loop)
|
||||
: m_event_loop(event_loop)
|
||||
{
|
||||
if (&m_event_loop != s_main_event_loop) {
|
||||
m_event_loop.take_pending_events_from(EventLoop::current());
|
||||
s_event_loop_stack->append(&event_loop);
|
||||
}
|
||||
}
|
||||
~EventLoopPusher()
|
||||
{
|
||||
if (&m_event_loop != s_main_event_loop) {
|
||||
s_event_loop_stack->take_last();
|
||||
EventLoop::current().take_pending_events_from(m_event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventLoop& m_event_loop;
|
||||
};
|
||||
|
||||
int EventLoop::exec()
|
||||
{
|
||||
EventLoopPusher pusher(*this);
|
||||
for (;;) {
|
||||
if (m_exit_requested)
|
||||
return m_exit_code;
|
||||
pump();
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void EventLoop::pump(WaitMode mode)
|
||||
{
|
||||
wait_for_event(mode);
|
||||
|
||||
decltype(m_queued_events) events;
|
||||
{
|
||||
LOCKER(m_private->lock);
|
||||
events = move(m_queued_events);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < events.size(); ++i) {
|
||||
auto& queued_event = events.at(i);
|
||||
auto receiver = queued_event.receiver.strong_ref();
|
||||
auto& event = *queued_event.event;
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
if (receiver)
|
||||
dbgln("Core::EventLoop: {} event {}", *receiver, event.type());
|
||||
#endif
|
||||
if (!receiver) {
|
||||
switch (event.type()) {
|
||||
case Event::Quit:
|
||||
ASSERT_NOT_REACHED();
|
||||
return;
|
||||
default:
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Event type {} with no receiver :(", event.type());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
} else if (event.type() == Event::Type::DeferredInvoke) {
|
||||
#ifdef DEFERRED_INVOKE_DEBUG
|
||||
dbgln("DeferredInvoke: receiver = {}", *receiver);
|
||||
#endif
|
||||
static_cast<DeferredInvocationEvent&>(event).m_invokee(*receiver);
|
||||
} else {
|
||||
NonnullRefPtr<Object> protector(*receiver);
|
||||
receiver->dispatch_event(event);
|
||||
}
|
||||
|
||||
if (m_exit_requested) {
|
||||
LOCKER(m_private->lock);
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop: Exit requested. Rejigging {} events.", events.size() - i);
|
||||
#endif
|
||||
decltype(m_queued_events) new_event_queue;
|
||||
new_event_queue.ensure_capacity(m_queued_events.size() + events.size());
|
||||
for (++i; i < events.size(); ++i)
|
||||
new_event_queue.unchecked_append(move(events[i]));
|
||||
new_event_queue.append(move(m_queued_events));
|
||||
m_queued_events = move(new_event_queue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::post_event(Object& receiver, NonnullOwnPtr<Event>&& event)
|
||||
{
|
||||
LOCKER(m_private->lock);
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop::post_event: ({}) << receivier={}, event={}", m_queued_events.size(), receiver, event);
|
||||
#endif
|
||||
m_queued_events.empend(receiver, move(event));
|
||||
}
|
||||
|
||||
SignalHandlers::SignalHandlers(int signo, void (*handle_signal)(int))
|
||||
: m_signo(signo)
|
||||
, m_original_handler(signal(signo, handle_signal))
|
||||
{
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop: Registered handler for signal {}", m_signo);
|
||||
#endif
|
||||
}
|
||||
|
||||
SignalHandlers::~SignalHandlers()
|
||||
{
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop: Unregistering handler for signal {}", m_signo);
|
||||
#endif
|
||||
signal(m_signo, m_original_handler);
|
||||
}
|
||||
|
||||
void SignalHandlers::dispatch()
|
||||
{
|
||||
TemporaryChange change(m_calling_handlers, true);
|
||||
for (auto& handler : m_handlers)
|
||||
handler.value(m_signo);
|
||||
if (!m_handlers_pending.is_empty()) {
|
||||
// Apply pending adds/removes
|
||||
for (auto& handler : m_handlers_pending) {
|
||||
if (handler.value) {
|
||||
auto result = m_handlers.set(handler.key, move(handler.value));
|
||||
ASSERT(result == AK::HashSetResult::InsertedNewEntry);
|
||||
} else {
|
||||
m_handlers.remove(handler.key);
|
||||
}
|
||||
}
|
||||
m_handlers_pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int SignalHandlers::add(Function<void(int)>&& handler)
|
||||
{
|
||||
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
|
||||
if (m_calling_handlers)
|
||||
m_handlers_pending.set(id, move(handler));
|
||||
else
|
||||
m_handlers.set(id, move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
bool SignalHandlers::remove(int handler_id)
|
||||
{
|
||||
ASSERT(handler_id != 0);
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers.find(handler_id);
|
||||
if (it != m_handlers.end()) {
|
||||
// Mark pending remove
|
||||
m_handlers_pending.set(handler_id, {});
|
||||
return true;
|
||||
}
|
||||
it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // already was marked as deleted
|
||||
it->value = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_handlers.remove(handler_id);
|
||||
}
|
||||
|
||||
void EventLoop::dispatch_signal(int signo)
|
||||
{
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signo);
|
||||
if (handlers != info.signal_handlers.end()) {
|
||||
// Make sure we bump the ref count while dispatching the handlers!
|
||||
// This allows a handler to unregister/register while the handlers
|
||||
// are being called!
|
||||
auto handler = handlers->value;
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop: dispatching signal {}", signo);
|
||||
#endif
|
||||
handler->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::handle_signal(int signo)
|
||||
{
|
||||
ASSERT(signo != 0);
|
||||
// We MUST check if the current pid still matches, because there
|
||||
// is a window between fork() and exec() where a signal delivered
|
||||
// to our fork could be inadvertedly routed to the parent process!
|
||||
if (getpid() == s_pid) {
|
||||
int nwritten = write(s_wake_pipe_fds[1], &signo, sizeof(signo));
|
||||
if (nwritten < 0) {
|
||||
perror("EventLoop::register_signal: write");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
// We're a fork who received a signal, reset s_pid
|
||||
s_pid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int EventLoop::register_signal(int signo, Function<void(int)> handler)
|
||||
{
|
||||
ASSERT(signo != 0);
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signo);
|
||||
if (handlers == info.signal_handlers.end()) {
|
||||
auto signal_handlers = adopt(*new SignalHandlers(signo, EventLoop::handle_signal));
|
||||
auto handler_id = signal_handlers->add(move(handler));
|
||||
info.signal_handlers.set(signo, move(signal_handlers));
|
||||
return handler_id;
|
||||
} else {
|
||||
return handlers->value->add(move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::unregister_signal(int handler_id)
|
||||
{
|
||||
ASSERT(handler_id != 0);
|
||||
int remove_signo = 0;
|
||||
auto& info = *signals_info();
|
||||
for (auto& h : info.signal_handlers) {
|
||||
auto& handlers = *h.value;
|
||||
if (handlers.remove(handler_id)) {
|
||||
if (handlers.is_empty())
|
||||
remove_signo = handlers.m_signo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_signo != 0)
|
||||
info.signal_handlers.remove(remove_signo);
|
||||
}
|
||||
|
||||
void EventLoop::notify_forked(ForkEvent event)
|
||||
{
|
||||
switch (event) {
|
||||
case ForkEvent::Child:
|
||||
s_main_event_loop = nullptr;
|
||||
s_event_loop_stack->clear();
|
||||
s_timers->clear();
|
||||
s_notifiers->clear();
|
||||
if (auto* info = signals_info<false>()) {
|
||||
info->signal_handlers.clear();
|
||||
info->next_signal_id = 0;
|
||||
}
|
||||
s_pid = 0;
|
||||
s_rpc_server = nullptr;
|
||||
s_rpc_clients.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void EventLoop::wait_for_event(WaitMode mode)
|
||||
{
|
||||
fd_set rfds;
|
||||
fd_set wfds;
|
||||
retry:
|
||||
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_fd_to_set(s_wake_pipe_fds[0], rfds);
|
||||
max_fd = max(max_fd, max_fd_added);
|
||||
for (auto& notifier : *s_notifiers) {
|
||||
if (notifier->event_mask() & Notifier::Read)
|
||||
add_fd_to_set(notifier->fd(), rfds);
|
||||
if (notifier->event_mask() & Notifier::Write)
|
||||
add_fd_to_set(notifier->fd(), wfds);
|
||||
if (notifier->event_mask() & Notifier::Exceptional)
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool queued_events_is_empty;
|
||||
{
|
||||
LOCKER(m_private->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 && queued_events_is_empty) {
|
||||
auto next_timer_expiration = get_next_timer_expiration();
|
||||
if (next_timer_expiration.has_value()) {
|
||||
timespec now_spec;
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
|
||||
now.tv_sec = now_spec.tv_sec;
|
||||
now.tv_usec = now_spec.tv_nsec / 1000;
|
||||
timeval_sub(next_timer_expiration.value(), now, timeout);
|
||||
if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_usec < 0)) {
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
}
|
||||
} else {
|
||||
should_wait_forever = true;
|
||||
}
|
||||
}
|
||||
|
||||
try_select_again:
|
||||
int marked_fd_count = select(max_fd + 1, &rfds, &wfds, nullptr, should_wait_forever ? nullptr : &timeout);
|
||||
if (marked_fd_count < 0) {
|
||||
int saved_errno = errno;
|
||||
if (saved_errno == EINTR) {
|
||||
if (m_exit_requested)
|
||||
return;
|
||||
goto try_select_again;
|
||||
}
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop::wait_for_event: {} ({}: {})", marked_fd_count, saved_errno, strerror(saved_errno));
|
||||
#endif
|
||||
// Blow up, similar to Core::safe_syscall.
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
if (FD_ISSET(s_wake_pipe_fds[0], &rfds)) {
|
||||
int wake_events[8];
|
||||
auto nread = read(s_wake_pipe_fds[0], wake_events, sizeof(wake_events));
|
||||
if (nread < 0) {
|
||||
perror("read from wake pipe");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
ASSERT(nread > 0);
|
||||
bool wake_requested = false;
|
||||
int event_count = nread / sizeof(wake_events[0]);
|
||||
for (int i = 0; i < event_count; i++) {
|
||||
if (wake_events[i] != 0)
|
||||
dispatch_signal(wake_events[i]);
|
||||
else
|
||||
wake_requested = true;
|
||||
}
|
||||
|
||||
if (!wake_requested && nread == sizeof(wake_events))
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (!s_timers->is_empty()) {
|
||||
timespec now_spec;
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
|
||||
now.tv_sec = now_spec.tv_sec;
|
||||
now.tv_usec = now_spec.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
for (auto& it : *s_timers) {
|
||||
auto& timer = *it.value;
|
||||
if (!timer.has_expired(now))
|
||||
continue;
|
||||
auto owner = timer.owner.strong_ref();
|
||||
if (timer.fire_when_not_visible == TimerShouldFireWhenNotVisible::No
|
||||
&& owner && !owner->is_visible_for_timer_purposes()) {
|
||||
continue;
|
||||
}
|
||||
#ifdef EVENTLOOP_DEBUG
|
||||
dbgln("Core::EventLoop: Timer {} has expired, sending Core::TimerEvent to {}", timer.timer_id, *owner);
|
||||
#endif
|
||||
if (owner)
|
||||
post_event(*owner, make<TimerEvent>(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->event_mask() & Notifier::Event::Read)
|
||||
post_event(*notifier, make<NotifierReadEvent>(notifier->fd()));
|
||||
}
|
||||
if (FD_ISSET(notifier->fd(), &wfds)) {
|
||||
if (notifier->event_mask() & Notifier::Event::Write)
|
||||
post_event(*notifier, make<NotifierWriteEvent>(notifier->fd()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool 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 EventLoopTimer::reload(const timeval& now)
|
||||
{
|
||||
fire_time = now;
|
||||
fire_time.tv_sec += interval / 1000;
|
||||
fire_time.tv_usec += (interval % 1000) * 1000;
|
||||
}
|
||||
|
||||
Optional<struct timeval> EventLoop::get_next_timer_expiration()
|
||||
{
|
||||
Optional<struct timeval> soonest {};
|
||||
for (auto& it : *s_timers) {
|
||||
auto& fire_time = it.value->fire_time;
|
||||
auto owner = it.value->owner.strong_ref();
|
||||
if (it.value->fire_when_not_visible == TimerShouldFireWhenNotVisible::No
|
||||
&& owner && !owner->is_visible_for_timer_purposes()) {
|
||||
continue;
|
||||
}
|
||||
if (!soonest.has_value() || fire_time.tv_sec < soonest.value().tv_sec || (fire_time.tv_sec == soonest.value().tv_sec && fire_time.tv_usec < soonest.value().tv_usec))
|
||||
soonest = fire_time;
|
||||
}
|
||||
return soonest;
|
||||
}
|
||||
|
||||
int EventLoop::register_timer(Object& object, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible fire_when_not_visible)
|
||||
{
|
||||
ASSERT(milliseconds >= 0);
|
||||
auto timer = make<EventLoopTimer>();
|
||||
timer->owner = object;
|
||||
timer->interval = milliseconds;
|
||||
timeval now;
|
||||
timespec now_spec;
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_spec);
|
||||
now.tv_sec = now_spec.tv_sec;
|
||||
now.tv_usec = now_spec.tv_nsec / 1000;
|
||||
timer->reload(now);
|
||||
timer->should_reload = should_reload;
|
||||
timer->fire_when_not_visible = fire_when_not_visible;
|
||||
int timer_id = s_id_allocator->allocate();
|
||||
timer->timer_id = timer_id;
|
||||
s_timers->set(timer_id, move(timer));
|
||||
return timer_id;
|
||||
}
|
||||
|
||||
bool EventLoop::unregister_timer(int timer_id)
|
||||
{
|
||||
s_id_allocator->deallocate(timer_id);
|
||||
auto it = s_timers->find(timer_id);
|
||||
if (it == s_timers->end())
|
||||
return false;
|
||||
s_timers->remove(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventLoop::register_notifier(Badge<Notifier>, Notifier& notifier)
|
||||
{
|
||||
s_notifiers->set(¬ifier);
|
||||
}
|
||||
|
||||
void EventLoop::unregister_notifier(Badge<Notifier>, Notifier& notifier)
|
||||
{
|
||||
s_notifiers->remove(¬ifier);
|
||||
}
|
||||
|
||||
void EventLoop::wake()
|
||||
{
|
||||
int wake_event = 0;
|
||||
int nwritten = write(s_wake_pipe_fds[1], &wake_event, sizeof(wake_event));
|
||||
if (nwritten < 0) {
|
||||
perror("EventLoop::wake: write");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
EventLoop::QueuedEvent::QueuedEvent(Object& receiver, NonnullOwnPtr<Event> event)
|
||||
: receiver(receiver)
|
||||
, event(move(event))
|
||||
{
|
||||
}
|
||||
|
||||
EventLoop::QueuedEvent::QueuedEvent(QueuedEvent&& other)
|
||||
: receiver(other.receiver)
|
||||
, event(move(other.event))
|
||||
{
|
||||
}
|
||||
|
||||
EventLoop::QueuedEvent::~QueuedEvent()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
122
Userland/Libraries/LibCore/EventLoop.h
Normal file
122
Userland/Libraries/LibCore/EventLoop.h
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class EventLoop {
|
||||
public:
|
||||
EventLoop();
|
||||
~EventLoop();
|
||||
|
||||
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(Object& receiver, NonnullOwnPtr<Event>&&);
|
||||
|
||||
static EventLoop& main();
|
||||
static EventLoop& current();
|
||||
|
||||
bool was_exit_requested() const { return m_exit_requested; }
|
||||
|
||||
static int register_timer(Object&, int milliseconds, bool should_reload, TimerShouldFireWhenNotVisible);
|
||||
static bool unregister_timer(int timer_id);
|
||||
|
||||
static void register_notifier(Badge<Notifier>, Notifier&);
|
||||
static void unregister_notifier(Badge<Notifier>, Notifier&);
|
||||
|
||||
void quit(int);
|
||||
void unquit();
|
||||
|
||||
void take_pending_events_from(EventLoop& other)
|
||||
{
|
||||
m_queued_events.append(move(other.m_queued_events));
|
||||
}
|
||||
|
||||
static void wake();
|
||||
|
||||
static int register_signal(int signo, Function<void(int)> handler);
|
||||
static void unregister_signal(int handler_id);
|
||||
|
||||
// Note: Boost uses Parent/Child/Prepare, but we don't really have anything
|
||||
// interesting to do in the parent or before forking.
|
||||
enum class ForkEvent {
|
||||
Child,
|
||||
};
|
||||
static void notify_forked(ForkEvent);
|
||||
|
||||
private:
|
||||
bool start_rpc_server();
|
||||
void wait_for_event(WaitMode);
|
||||
Optional<struct timeval> get_next_timer_expiration();
|
||||
static void dispatch_signal(int);
|
||||
static void handle_signal(int);
|
||||
|
||||
struct QueuedEvent {
|
||||
AK_MAKE_NONCOPYABLE(QueuedEvent);
|
||||
|
||||
public:
|
||||
QueuedEvent(Object& receiver, NonnullOwnPtr<Event>);
|
||||
QueuedEvent(QueuedEvent&&);
|
||||
~QueuedEvent();
|
||||
|
||||
WeakPtr<Object> receiver;
|
||||
NonnullOwnPtr<Event> event;
|
||||
};
|
||||
|
||||
Vector<QueuedEvent, 64> m_queued_events;
|
||||
static pid_t s_pid;
|
||||
|
||||
bool m_exit_requested { false };
|
||||
int m_exit_code { 0 };
|
||||
|
||||
static int s_wake_pipe_fds[2];
|
||||
|
||||
struct Private;
|
||||
NonnullOwnPtr<Private> m_private;
|
||||
};
|
||||
|
||||
}
|
270
Userland/Libraries/LibCore/File.cpp
Normal file
270
Userland/Libraries/LibCore/File.cpp
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef __serenity__
|
||||
# include <Kernel/API/Syscall.h>
|
||||
#endif
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Result<NonnullRefPtr<File>, String> File::open(const String& filename, IODevice::OpenMode mode, mode_t permissions)
|
||||
{
|
||||
auto file = File::construct(filename);
|
||||
if (!file->open_impl(mode, permissions))
|
||||
return String(file->error_string());
|
||||
return file;
|
||||
}
|
||||
|
||||
File::File(const StringView& filename, Object* parent)
|
||||
: IODevice(parent)
|
||||
, m_filename(filename)
|
||||
{
|
||||
}
|
||||
|
||||
File::~File()
|
||||
{
|
||||
if (m_should_close_file_descriptor == ShouldCloseFileDescriptor::Yes && mode() != NotOpen)
|
||||
close();
|
||||
}
|
||||
|
||||
bool File::open(int fd, IODevice::OpenMode mode, ShouldCloseFileDescriptor should_close)
|
||||
{
|
||||
set_fd(fd);
|
||||
set_mode(mode);
|
||||
m_should_close_file_descriptor = should_close;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::open(IODevice::OpenMode mode)
|
||||
{
|
||||
return open_impl(mode, 0666);
|
||||
}
|
||||
|
||||
bool File::open_impl(IODevice::OpenMode mode, mode_t permissions)
|
||||
{
|
||||
ASSERT(!m_filename.is_null());
|
||||
int flags = 0;
|
||||
if ((mode & IODevice::ReadWrite) == IODevice::ReadWrite) {
|
||||
flags |= O_RDWR | O_CREAT;
|
||||
} else if (mode & IODevice::ReadOnly) {
|
||||
flags |= O_RDONLY;
|
||||
} else if (mode & IODevice::WriteOnly) {
|
||||
flags |= O_WRONLY | O_CREAT;
|
||||
bool should_truncate = !((mode & IODevice::Append) || (mode & IODevice::MustBeNew));
|
||||
if (should_truncate)
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
if (mode & IODevice::Append)
|
||||
flags |= O_APPEND;
|
||||
if (mode & IODevice::Truncate)
|
||||
flags |= O_TRUNC;
|
||||
if (mode & IODevice::MustBeNew)
|
||||
flags |= O_EXCL;
|
||||
int fd = ::open(m_filename.characters(), flags, permissions);
|
||||
if (fd < 0) {
|
||||
set_error(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
set_fd(fd);
|
||||
set_mode(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::is_directory() const
|
||||
{
|
||||
struct stat stat;
|
||||
if (fstat(fd(), &stat) < 0)
|
||||
return false;
|
||||
return S_ISDIR(stat.st_mode);
|
||||
}
|
||||
|
||||
bool File::is_directory(const String& filename)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(filename.characters(), &st) < 0)
|
||||
return false;
|
||||
return S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
bool File::exists(const String& filename)
|
||||
{
|
||||
struct stat st;
|
||||
return stat(filename.characters(), &st) == 0;
|
||||
}
|
||||
|
||||
String File::real_path_for(const String& filename)
|
||||
{
|
||||
if (filename.is_null())
|
||||
return {};
|
||||
auto* path = realpath(filename.characters(), nullptr);
|
||||
String real_path(path);
|
||||
free(path);
|
||||
return real_path;
|
||||
}
|
||||
|
||||
bool File::ensure_parent_directories(const String& path)
|
||||
{
|
||||
ASSERT(path.starts_with("/"));
|
||||
|
||||
int saved_errno = 0;
|
||||
ScopeGuard restore_errno = [&saved_errno] { errno = saved_errno; };
|
||||
|
||||
char* parent_buffer = strdup(path.characters());
|
||||
ScopeGuard free_buffer = [parent_buffer] { free(parent_buffer); };
|
||||
|
||||
const char* parent = dirname(parent_buffer);
|
||||
|
||||
int rc = mkdir(parent, 0755);
|
||||
saved_errno = errno;
|
||||
|
||||
if (rc == 0 || errno == EEXIST)
|
||||
return true;
|
||||
|
||||
if (errno != ENOENT)
|
||||
return false;
|
||||
|
||||
bool ok = ensure_parent_directories(parent);
|
||||
saved_errno = errno;
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
rc = mkdir(parent, 0755);
|
||||
saved_errno = errno;
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
#ifdef __serenity__
|
||||
|
||||
String File::read_link(const StringView& link_path)
|
||||
{
|
||||
// First, try using a 64-byte buffer, that ought to be enough for anybody.
|
||||
char small_buffer[64];
|
||||
Syscall::SC_readlink_params small_params {
|
||||
{ link_path.characters_without_null_termination(), link_path.length() },
|
||||
{ small_buffer, sizeof(small_buffer) }
|
||||
};
|
||||
int rc = syscall(SC_readlink, &small_params);
|
||||
if (rc < 0) {
|
||||
errno = -rc;
|
||||
return {};
|
||||
}
|
||||
size_t size = rc;
|
||||
// If the call was successful, the syscall (unlike the LibC wrapper)
|
||||
// returns the full size of the link. Let's see if our small buffer
|
||||
// was enough to read the whole link.
|
||||
if (size <= sizeof(small_buffer))
|
||||
return { small_buffer, size };
|
||||
// Nope, but at least now we know the right size.
|
||||
char* large_buffer_ptr;
|
||||
auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr);
|
||||
Syscall::SC_readlink_params large_params {
|
||||
{ link_path.characters_without_null_termination(), link_path.length() },
|
||||
{ large_buffer_ptr, (size_t)size }
|
||||
};
|
||||
rc = syscall(SC_readlink, &large_params);
|
||||
if (rc < 0) {
|
||||
errno = -rc;
|
||||
return {};
|
||||
}
|
||||
size_t new_size = rc;
|
||||
if (new_size == size)
|
||||
return { *large_buffer };
|
||||
|
||||
// If we're here, the symlink has changed while we were looking at it.
|
||||
// If it became shorter, our buffer is valid, we just have to trim it a bit.
|
||||
if (new_size < size)
|
||||
return { large_buffer_ptr, new_size };
|
||||
// Otherwise, here's not much we can do, unless we want to loop endlessly
|
||||
// in this case. Let's leave it up to the caller whether to loop.
|
||||
errno = -EAGAIN;
|
||||
return {};
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// This is a sad version for other systems. It has to always make a copy of the
|
||||
// link path, and to always make two syscalls to get the right size first.
|
||||
String File::read_link(const StringView& link_path)
|
||||
{
|
||||
String link_path_str = link_path;
|
||||
struct stat statbuf;
|
||||
int rc = lstat(link_path_str.characters(), &statbuf);
|
||||
if (rc < 0)
|
||||
return {};
|
||||
char* buffer_ptr;
|
||||
auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr);
|
||||
rc = readlink(link_path_str.characters(), buffer_ptr, statbuf.st_size);
|
||||
if (rc < 0)
|
||||
return {};
|
||||
// (See above.)
|
||||
if (rc == statbuf.st_size)
|
||||
return { *buffer };
|
||||
return { buffer_ptr, (size_t)rc };
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static RefPtr<File> stdin_file;
|
||||
static RefPtr<File> stdout_file;
|
||||
static RefPtr<File> stderr_file;
|
||||
|
||||
NonnullRefPtr<File> File::standard_input()
|
||||
{
|
||||
if (!stdin_file) {
|
||||
stdin_file = File::construct();
|
||||
stdin_file->open(STDIN_FILENO, IODevice::ReadOnly, ShouldCloseFileDescriptor::No);
|
||||
}
|
||||
return *stdin_file;
|
||||
}
|
||||
|
||||
NonnullRefPtr<File> File::standard_output()
|
||||
{
|
||||
if (!stdout_file) {
|
||||
stdout_file = File::construct();
|
||||
stdout_file->open(STDOUT_FILENO, IODevice::WriteOnly, ShouldCloseFileDescriptor::No);
|
||||
}
|
||||
return *stdout_file;
|
||||
}
|
||||
|
||||
NonnullRefPtr<File> File::standard_error()
|
||||
{
|
||||
if (!stderr_file) {
|
||||
stderr_file = File::construct();
|
||||
stderr_file->open(STDERR_FILENO, IODevice::WriteOnly, ShouldCloseFileDescriptor::No);
|
||||
}
|
||||
return *stderr_file;
|
||||
}
|
||||
}
|
78
Userland/Libraries/LibCore/File.h
Normal file
78
Userland/Libraries/LibCore/File.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Result.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class File final : public IODevice {
|
||||
C_OBJECT(File)
|
||||
public:
|
||||
virtual ~File() override;
|
||||
|
||||
static Result<NonnullRefPtr<File>, String> open(const String& filename, IODevice::OpenMode, mode_t = 0644);
|
||||
|
||||
String filename() const { return m_filename; }
|
||||
void set_filename(const StringView& filename) { m_filename = filename; }
|
||||
|
||||
bool is_directory() const;
|
||||
static bool is_directory(const String& filename);
|
||||
|
||||
static bool exists(const String& filename);
|
||||
static String real_path_for(const String& filename);
|
||||
static String read_link(const StringView& link_path);
|
||||
static bool ensure_parent_directories(const String& path);
|
||||
|
||||
virtual bool open(IODevice::OpenMode) override;
|
||||
|
||||
enum class ShouldCloseFileDescriptor {
|
||||
No = 0,
|
||||
Yes
|
||||
};
|
||||
bool open(int fd, IODevice::OpenMode, ShouldCloseFileDescriptor);
|
||||
|
||||
static NonnullRefPtr<File> standard_input();
|
||||
static NonnullRefPtr<File> standard_output();
|
||||
static NonnullRefPtr<File> standard_error();
|
||||
|
||||
private:
|
||||
File(Object* parent = nullptr)
|
||||
: IODevice(parent)
|
||||
{
|
||||
}
|
||||
explicit File(const StringView&, Object* parent = nullptr);
|
||||
|
||||
bool open_impl(IODevice::OpenMode, mode_t);
|
||||
|
||||
String m_filename;
|
||||
ShouldCloseFileDescriptor m_should_close_file_descriptor { ShouldCloseFileDescriptor::Yes };
|
||||
};
|
||||
|
||||
}
|
171
Userland/Libraries/LibCore/FileStream.h
Normal file
171
Userland/Libraries/LibCore/FileStream.h
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Buffered.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class InputFileStream final : public InputStream {
|
||||
public:
|
||||
explicit InputFileStream(NonnullRefPtr<File> file)
|
||||
: m_file(file)
|
||||
{
|
||||
}
|
||||
|
||||
static Result<InputFileStream, String> open(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::ReadOnly, mode_t permissions = 0644)
|
||||
{
|
||||
ASSERT((mode & 0xf) == IODevice::OpenMode::ReadOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
|
||||
|
||||
auto file_result = File::open(filename, mode, permissions);
|
||||
|
||||
if (file_result.is_error())
|
||||
return file_result.error();
|
||||
|
||||
return InputFileStream { file_result.value() };
|
||||
}
|
||||
|
||||
static Result<Buffered<InputFileStream>, String> open_buffered(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::ReadOnly, mode_t permissions = 0644)
|
||||
{
|
||||
ASSERT((mode & 0xf) == IODevice::OpenMode::ReadOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
|
||||
|
||||
auto file_result = File::open(filename, mode, permissions);
|
||||
|
||||
if (file_result.is_error())
|
||||
return file_result.error();
|
||||
|
||||
return Buffered<InputFileStream> { file_result.value() };
|
||||
}
|
||||
|
||||
size_t read(Bytes bytes) override
|
||||
{
|
||||
if (has_any_error())
|
||||
return 0;
|
||||
|
||||
const auto buffer = m_file->read(bytes.size());
|
||||
return buffer.bytes().copy_to(bytes);
|
||||
}
|
||||
|
||||
bool read_or_error(Bytes bytes) override
|
||||
{
|
||||
if (read(bytes) < bytes.size()) {
|
||||
set_fatal_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool discard_or_error(size_t count) override { return m_file->seek(count, IODevice::SeekMode::FromCurrentPosition); }
|
||||
|
||||
bool unreliable_eof() const override { return m_file->eof(); }
|
||||
|
||||
void close()
|
||||
{
|
||||
if (!m_file->close())
|
||||
set_fatal_error();
|
||||
}
|
||||
|
||||
private:
|
||||
NonnullRefPtr<File> m_file;
|
||||
};
|
||||
|
||||
class OutputFileStream : public OutputStream {
|
||||
public:
|
||||
explicit OutputFileStream(NonnullRefPtr<File> file)
|
||||
: m_file(file)
|
||||
{
|
||||
}
|
||||
|
||||
static Result<OutputFileStream, String> open(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::WriteOnly, mode_t permissions = 0644)
|
||||
{
|
||||
ASSERT((mode & 0xf) == IODevice::OpenMode::WriteOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
|
||||
|
||||
auto file_result = File::open(filename, mode, permissions);
|
||||
|
||||
if (file_result.is_error())
|
||||
return file_result.error();
|
||||
|
||||
return OutputFileStream { file_result.value() };
|
||||
}
|
||||
|
||||
static Result<Buffered<OutputFileStream>, String> open_buffered(StringView filename, IODevice::OpenMode mode = IODevice::OpenMode::WriteOnly, mode_t permissions = 0644)
|
||||
{
|
||||
ASSERT((mode & 0xf) == IODevice::OpenMode::WriteOnly || (mode & 0xf) == IODevice::OpenMode::ReadWrite);
|
||||
|
||||
auto file_result = File::open(filename, mode, permissions);
|
||||
|
||||
if (file_result.is_error())
|
||||
return file_result.error();
|
||||
|
||||
return Buffered<OutputFileStream> { file_result.value() };
|
||||
}
|
||||
|
||||
static OutputFileStream standard_output()
|
||||
{
|
||||
return OutputFileStream { Core::File::standard_output() };
|
||||
}
|
||||
|
||||
static Buffered<OutputFileStream> stdout_buffered()
|
||||
{
|
||||
return Buffered<OutputFileStream> { Core::File::standard_output() };
|
||||
}
|
||||
|
||||
size_t write(ReadonlyBytes bytes) override
|
||||
{
|
||||
if (!m_file->write(bytes.data(), bytes.size())) {
|
||||
set_fatal_error();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
bool write_or_error(ReadonlyBytes bytes) override
|
||||
{
|
||||
if (write(bytes) < bytes.size()) {
|
||||
set_fatal_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (!m_file->close())
|
||||
set_fatal_error();
|
||||
}
|
||||
|
||||
private:
|
||||
NonnullRefPtr<File> m_file;
|
||||
};
|
||||
|
||||
}
|
61
Userland/Libraries/LibCore/Forward.h
Normal file
61
Userland/Libraries/LibCore/Forward.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ArgsParser;
|
||||
class ChildEvent;
|
||||
class ConfigFile;
|
||||
class CustomEvent;
|
||||
class DateTime;
|
||||
class DirIterator;
|
||||
class ElapsedTimer;
|
||||
class Event;
|
||||
class EventLoop;
|
||||
class File;
|
||||
class IODevice;
|
||||
class LocalServer;
|
||||
class LocalSocket;
|
||||
class MimeData;
|
||||
class NetworkJob;
|
||||
class NetworkResponse;
|
||||
class Notifier;
|
||||
class Object;
|
||||
class ProcessStatisticsReader;
|
||||
class Socket;
|
||||
class SocketAddress;
|
||||
class TCPServer;
|
||||
class TCPSocket;
|
||||
class Timer;
|
||||
class TimerEvent;
|
||||
class UDPServer;
|
||||
class UDPSocket;
|
||||
|
||||
enum class TimerShouldFireWhenNotVisible;
|
||||
|
||||
}
|
70
Userland/Libraries/LibCore/GetPassword.cpp
Normal file
70
Userland/Libraries/LibCore/GetPassword.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
|
||||
* Copyright (c) 2021, Emanuele Torre <torreemanuele6@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/GetPassword.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <termios.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Result<String, OSError> get_password(const StringView& prompt)
|
||||
{
|
||||
if (write(STDOUT_FILENO, prompt.characters_without_null_termination(), prompt.length()) < 0)
|
||||
return OSError(errno);
|
||||
|
||||
termios original {};
|
||||
if (tcgetattr(STDIN_FILENO, &original) < 0)
|
||||
return OSError(errno);
|
||||
|
||||
termios no_echo = original;
|
||||
no_echo.c_lflag &= ~ECHO;
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &no_echo) < 0)
|
||||
return OSError(errno);
|
||||
|
||||
char* password = nullptr;
|
||||
size_t n = 0;
|
||||
|
||||
auto line_length = getline(&password, &n, stdin);
|
||||
auto saved_errno = errno;
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
|
||||
putchar('\n');
|
||||
|
||||
if (line_length < 0)
|
||||
return OSError(saved_errno);
|
||||
|
||||
ASSERT(line_length != 0);
|
||||
|
||||
// Remove trailing '\n' read by getline().
|
||||
password[line_length - 1] = '\0';
|
||||
|
||||
String s(password);
|
||||
free(password);
|
||||
return s;
|
||||
}
|
||||
}
|
37
Userland/Libraries/LibCore/GetPassword.h
Normal file
37
Userland/Libraries/LibCore/GetPassword.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OSError.h>
|
||||
#include <AK/Result.h>
|
||||
#include <AK/String.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Result<String, OSError> get_password(const StringView& prompt = "Password: ");
|
||||
|
||||
}
|
165
Userland/Libraries/LibCore/Gzip.cpp
Normal file
165
Userland/Libraries/LibCore/Gzip.cpp
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCore/Gzip.h>
|
||||
#include <LibCore/puff.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
|
||||
//#define DEBUG_GZIP
|
||||
|
||||
namespace Core {
|
||||
|
||||
bool Gzip::is_compressed(const ByteBuffer& data)
|
||||
{
|
||||
return data.size() > 2 && data[0] == 0x1F && data[1] == 0x8b;
|
||||
}
|
||||
|
||||
// skips the gzip header
|
||||
// see: https://tools.ietf.org/html/rfc1952#page-5
|
||||
static Optional<ByteBuffer> get_gzip_payload(const ByteBuffer& data)
|
||||
{
|
||||
size_t current = 0;
|
||||
auto read_byte = [&]() {
|
||||
if (current >= data.size()) {
|
||||
ASSERT_NOT_REACHED();
|
||||
return (u8)0;
|
||||
}
|
||||
return data[current++];
|
||||
};
|
||||
|
||||
#ifdef DEBUG_GZIP
|
||||
dbgln("get_gzip_payload: Skipping over gzip header.");
|
||||
#endif
|
||||
|
||||
// Magic Header
|
||||
if (read_byte() != 0x1F || read_byte() != 0x8B) {
|
||||
dbgln("get_gzip_payload: Wrong magic number.");
|
||||
return Optional<ByteBuffer>();
|
||||
}
|
||||
|
||||
// Compression method
|
||||
auto method = read_byte();
|
||||
if (method != 8) {
|
||||
dbgln("get_gzip_payload: Wrong compression method={}", method);
|
||||
return Optional<ByteBuffer>();
|
||||
}
|
||||
|
||||
u8 flags = read_byte();
|
||||
|
||||
// Timestamp, Extra flags, OS
|
||||
current += 6;
|
||||
|
||||
// FEXTRA
|
||||
if (flags & 4) {
|
||||
u16 length = read_byte() & read_byte() << 8;
|
||||
dbgln("get_gzip_payload: Header has FEXTRA flag set. length={}", length);
|
||||
current += length;
|
||||
}
|
||||
|
||||
// FNAME
|
||||
if (flags & 8) {
|
||||
dbgln("get_gzip_payload: Header has FNAME flag set.");
|
||||
while (read_byte() != '\0')
|
||||
;
|
||||
}
|
||||
|
||||
// FCOMMENT
|
||||
if (flags & 16) {
|
||||
dbgln("get_gzip_payload: Header has FCOMMENT flag set.");
|
||||
while (read_byte() != '\0')
|
||||
;
|
||||
}
|
||||
|
||||
// FHCRC
|
||||
if (flags & 2) {
|
||||
dbgln("get_gzip_payload: Header has FHCRC flag set.");
|
||||
current += 2;
|
||||
}
|
||||
|
||||
auto new_size = data.size() - current;
|
||||
#ifdef DEBUG_GZIP
|
||||
dbg() << "get_gzip_payload: Returning slice from " << current << " with size " << new_size;
|
||||
#endif
|
||||
return data.slice(current, new_size);
|
||||
}
|
||||
|
||||
Optional<ByteBuffer> Gzip::decompress(const ByteBuffer& data)
|
||||
{
|
||||
ASSERT(is_compressed(data));
|
||||
|
||||
#ifdef DEBUG_GZIP
|
||||
dbg() << "Gzip::decompress: Decompressing gzip compressed data. Size = " << data.size();
|
||||
#endif
|
||||
auto optional_payload = get_gzip_payload(data);
|
||||
if (!optional_payload.has_value()) {
|
||||
return Optional<ByteBuffer>();
|
||||
}
|
||||
|
||||
auto source = optional_payload.value();
|
||||
unsigned long source_len = source.size();
|
||||
auto destination = ByteBuffer::create_uninitialized(1024);
|
||||
while (true) {
|
||||
unsigned long destination_len = destination.size();
|
||||
|
||||
#ifdef DEBUG_GZIP
|
||||
dbg() << "Gzip::decompress: Calling puff()\n"
|
||||
<< " destination_data = " << destination.data() << "\n"
|
||||
<< " destination_len = " << destination_len << "\n"
|
||||
<< " source_data = " << source.data() << "\n"
|
||||
<< " source_len = " << source_len;
|
||||
#endif
|
||||
|
||||
auto puff_ret = puff(
|
||||
destination.data(), &destination_len,
|
||||
source.data(), &source_len);
|
||||
|
||||
if (puff_ret == 0) {
|
||||
#ifdef DEBUG_GZIP
|
||||
dbgln("Gzip::decompress: Decompression success.");
|
||||
#endif
|
||||
destination.trim(destination_len);
|
||||
break;
|
||||
}
|
||||
|
||||
if (puff_ret == 1) {
|
||||
// FIXME: Find a better way of decompressing without needing to try over and over again.
|
||||
#ifdef DEBUG_GZIP
|
||||
dbgln("Gzip::decompress: Output buffer exhausted. Growing.");
|
||||
#endif
|
||||
destination.grow(destination.size() * 2);
|
||||
} else {
|
||||
dbgln("Gzip::decompress: Error. puff() returned: {}", puff_ret);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
}
|
41
Userland/Libraries/LibCore/Gzip.h
Normal file
41
Userland/Libraries/LibCore/Gzip.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Gzip {
|
||||
public:
|
||||
static bool is_compressed(const ByteBuffer& data);
|
||||
static Optional<ByteBuffer> decompress(const ByteBuffer& data);
|
||||
};
|
||||
|
||||
}
|
336
Userland/Libraries/LibCore/IODevice.cpp
Normal file
336
Userland/Libraries/LibCore/IODevice.cpp
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/PrintfImplementation.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
#include <LibCore/SyscallUtils.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
IODevice::IODevice(Object* parent)
|
||||
: Object(parent)
|
||||
{
|
||||
}
|
||||
|
||||
IODevice::~IODevice()
|
||||
{
|
||||
}
|
||||
|
||||
const char* IODevice::error_string() const
|
||||
{
|
||||
return strerror(m_error);
|
||||
}
|
||||
|
||||
int IODevice::read(u8* buffer, int length)
|
||||
{
|
||||
auto read_buffer = read(length);
|
||||
if (read_buffer.is_null())
|
||||
return 0;
|
||||
memcpy(buffer, read_buffer.data(), length);
|
||||
return read_buffer.size();
|
||||
}
|
||||
|
||||
ByteBuffer IODevice::read(size_t max_size)
|
||||
{
|
||||
if (m_fd < 0)
|
||||
return {};
|
||||
if (!max_size)
|
||||
return {};
|
||||
auto buffer = ByteBuffer::create_uninitialized(max_size);
|
||||
auto* buffer_ptr = (char*)buffer.data();
|
||||
size_t remaining_buffer_space = buffer.size();
|
||||
size_t 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 IODevice::can_read_from_fd() const
|
||||
{
|
||||
// FIXME: Can we somehow remove this once Core::Socket is implemented using non-blocking sockets?
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(m_fd, &rfds);
|
||||
struct timeval timeout {
|
||||
0, 0
|
||||
};
|
||||
int rc = Core::safe_syscall(select, m_fd + 1, &rfds, nullptr, nullptr, &timeout);
|
||||
if (rc < 0) {
|
||||
// NOTE: We don't set m_error here.
|
||||
perror("IODevice::can_read: select");
|
||||
return false;
|
||||
}
|
||||
return FD_ISSET(m_fd, &rfds);
|
||||
}
|
||||
|
||||
bool IODevice::can_read_line() const
|
||||
{
|
||||
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();
|
||||
if (m_eof && !m_buffered_data.is_empty())
|
||||
return true;
|
||||
return m_buffered_data.contains_slow('\n');
|
||||
}
|
||||
|
||||
bool IODevice::can_read() const
|
||||
{
|
||||
return !m_buffered_data.is_empty() || can_read_from_fd();
|
||||
}
|
||||
|
||||
ByteBuffer IODevice::read_all()
|
||||
{
|
||||
off_t file_size = 0;
|
||||
struct stat st;
|
||||
int rc = fstat(fd(), &st);
|
||||
if (rc == 0)
|
||||
file_size = st.st_size;
|
||||
|
||||
Vector<u8> data;
|
||||
data.ensure_capacity(file_size);
|
||||
|
||||
if (!m_buffered_data.is_empty()) {
|
||||
data.append(m_buffered_data.data(), m_buffered_data.size());
|
||||
m_buffered_data.clear();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
char read_buffer[4096];
|
||||
int nread = ::read(m_fd, read_buffer, sizeof(read_buffer));
|
||||
if (nread < 0) {
|
||||
set_error(errno);
|
||||
break;
|
||||
}
|
||||
if (nread == 0) {
|
||||
set_eof(true);
|
||||
break;
|
||||
}
|
||||
data.append((const u8*)read_buffer, nread);
|
||||
}
|
||||
if (data.is_empty())
|
||||
return {};
|
||||
return ByteBuffer::copy(data.data(), data.size());
|
||||
}
|
||||
|
||||
String IODevice::read_line(size_t 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("IODevice::read_line: At EOF but there's more than max_size(%zu) buffered\n", max_size);
|
||||
return {};
|
||||
}
|
||||
auto line = String((const char*)m_buffered_data.data(), m_buffered_data.size(), Chomp);
|
||||
m_buffered_data.clear();
|
||||
return line;
|
||||
}
|
||||
auto line = ByteBuffer::create_uninitialized(max_size + 1);
|
||||
size_t 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.trim(line_index);
|
||||
return String::copy(line, Chomp);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool IODevice::populate_read_buffer() const
|
||||
{
|
||||
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 IODevice::close()
|
||||
{
|
||||
if (fd() < 0 || mode() == NotOpen)
|
||||
return false;
|
||||
int rc = ::close(fd());
|
||||
if (rc < 0) {
|
||||
set_error(errno);
|
||||
return false;
|
||||
}
|
||||
set_fd(-1);
|
||||
set_mode(IODevice::NotOpen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IODevice::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 IODevice::truncate(off_t size)
|
||||
{
|
||||
int rc = ftruncate(m_fd, size);
|
||||
if (rc < 0) {
|
||||
set_error(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IODevice::write(const u8* data, int size)
|
||||
{
|
||||
int rc = ::write(m_fd, data, size);
|
||||
if (rc < 0) {
|
||||
perror("IODevice::write: write");
|
||||
set_error(errno);
|
||||
return false;
|
||||
}
|
||||
return rc == size;
|
||||
}
|
||||
|
||||
int IODevice::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) {
|
||||
write((const u8*)&ch, 1);
|
||||
},
|
||||
nullptr, format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void IODevice::set_fd(int fd)
|
||||
{
|
||||
if (m_fd == fd)
|
||||
return;
|
||||
|
||||
m_fd = fd;
|
||||
did_update_fd(fd);
|
||||
}
|
||||
|
||||
bool IODevice::write(const StringView& v)
|
||||
{
|
||||
return write((const u8*)v.characters_without_null_termination(), v.length());
|
||||
}
|
||||
|
||||
LineIterator::LineIterator(IODevice& device, bool is_end)
|
||||
: m_device(device)
|
||||
, m_is_end(is_end)
|
||||
{
|
||||
++*this;
|
||||
}
|
||||
|
||||
bool LineIterator::at_end() const
|
||||
{
|
||||
return m_device->eof();
|
||||
}
|
||||
|
||||
LineIterator& LineIterator::operator++()
|
||||
{
|
||||
m_buffer = m_device->read_line();
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
133
Userland/Libraries/LibCore/IODevice.h
Normal file
133
Userland/Libraries/LibCore/IODevice.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
// This is not necessarily a valid iterator in all contexts,
|
||||
// if we had concepts, this would be InputIterator, not Copyable, Movable.
|
||||
class LineIterator {
|
||||
AK_MAKE_NONCOPYABLE(LineIterator);
|
||||
|
||||
public:
|
||||
explicit LineIterator(IODevice&, bool is_end = false);
|
||||
|
||||
bool operator==(const LineIterator& other) const { return &other == this || (at_end() && other.is_end()) || (other.at_end() && is_end()); }
|
||||
bool is_end() const { return m_is_end; }
|
||||
bool at_end() const;
|
||||
|
||||
LineIterator& operator++();
|
||||
|
||||
StringView operator*() const { return m_buffer; }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<IODevice> m_device;
|
||||
bool m_is_end { false };
|
||||
String m_buffer;
|
||||
};
|
||||
|
||||
class IODevice : public Object {
|
||||
C_OBJECT_ABSTRACT(IODevice)
|
||||
public:
|
||||
enum OpenMode {
|
||||
NotOpen = 0,
|
||||
ReadOnly = 1,
|
||||
WriteOnly = 2,
|
||||
ReadWrite = 3,
|
||||
Append = 4,
|
||||
Truncate = 8,
|
||||
MustBeNew = 16,
|
||||
};
|
||||
|
||||
virtual ~IODevice() override;
|
||||
|
||||
int fd() const { return m_fd; }
|
||||
unsigned mode() const { return m_mode; }
|
||||
bool is_open() const { return m_mode != NotOpen; }
|
||||
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; }
|
||||
|
||||
int read(u8* buffer, int length);
|
||||
|
||||
ByteBuffer read(size_t max_size);
|
||||
ByteBuffer read_all();
|
||||
String read_line(size_t max_size = 16384);
|
||||
|
||||
bool write(const u8*, int size);
|
||||
bool write(const StringView&);
|
||||
|
||||
bool truncate(off_t);
|
||||
|
||||
bool can_read_line() const;
|
||||
|
||||
bool can_read() const;
|
||||
|
||||
enum class SeekMode {
|
||||
SetPosition,
|
||||
FromCurrentPosition,
|
||||
FromEndPosition,
|
||||
};
|
||||
|
||||
bool seek(i64, SeekMode = SeekMode::SetPosition, off_t* = nullptr);
|
||||
|
||||
virtual bool open(IODevice::OpenMode) = 0;
|
||||
virtual bool close();
|
||||
|
||||
int printf(const char*, ...);
|
||||
|
||||
LineIterator line_begin() & { return LineIterator(*this); }
|
||||
LineIterator line_end() { return LineIterator(*this, true); }
|
||||
|
||||
protected:
|
||||
explicit IODevice(Object* parent = nullptr);
|
||||
|
||||
void set_fd(int);
|
||||
void set_mode(OpenMode mode) { m_mode = mode; }
|
||||
void set_error(int error) const { m_error = error; }
|
||||
void set_eof(bool eof) const { m_eof = eof; }
|
||||
|
||||
virtual void did_update_fd(int) { }
|
||||
|
||||
private:
|
||||
bool populate_read_buffer() const;
|
||||
bool can_read_from_fd() const;
|
||||
|
||||
int m_fd { -1 };
|
||||
OpenMode m_mode { NotOpen };
|
||||
mutable int m_error { 0 };
|
||||
mutable bool m_eof { false };
|
||||
mutable Vector<u8> m_buffered_data;
|
||||
};
|
||||
|
||||
}
|
61
Userland/Libraries/LibCore/IODeviceStreamReader.h
Normal file
61
Userland/Libraries/LibCore/IODeviceStreamReader.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class IODeviceStreamReader {
|
||||
public:
|
||||
IODeviceStreamReader(IODevice& device)
|
||||
: m_device(device)
|
||||
{
|
||||
}
|
||||
|
||||
bool handle_read_failure()
|
||||
{
|
||||
return exchange(m_had_failure, false);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
IODeviceStreamReader& operator>>(T& value)
|
||||
{
|
||||
int nread = m_device.read((u8*)&value, sizeof(T));
|
||||
ASSERT(nread == sizeof(T));
|
||||
if (nread != sizeof(T))
|
||||
m_had_failure = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
IODevice& m_device;
|
||||
bool m_had_failure { false };
|
||||
};
|
||||
|
||||
}
|
161
Userland/Libraries/LibCore/LocalServer.cpp
Normal file
161
Userland/Libraries/LibCore/LocalServer.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/LocalSocket.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
LocalServer::LocalServer(Object* parent)
|
||||
: Object(parent)
|
||||
{
|
||||
}
|
||||
|
||||
LocalServer::~LocalServer()
|
||||
{
|
||||
if (m_fd >= 0)
|
||||
::close(m_fd);
|
||||
}
|
||||
|
||||
bool LocalServer::take_over_from_system_server()
|
||||
{
|
||||
if (m_listening)
|
||||
return false;
|
||||
|
||||
constexpr auto socket_takeover = "SOCKET_TAKEOVER";
|
||||
|
||||
if (getenv(socket_takeover)) {
|
||||
// Sanity check: it has to be a socket.
|
||||
struct stat stat;
|
||||
int rc = fstat(3, &stat);
|
||||
if (rc == 0 && S_ISSOCK(stat.st_mode)) {
|
||||
// The SystemServer has passed us the socket as fd 3,
|
||||
// so use that instead of creating our own.
|
||||
m_fd = 3;
|
||||
// It had to be !CLOEXEC for obvious reasons, but we
|
||||
// don't need it to be !CLOEXEC anymore, so set the
|
||||
// CLOEXEC flag now.
|
||||
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
||||
// We wouldn't want our children to think we're passing
|
||||
// them a socket either, so unset the env variable.
|
||||
unsetenv(socket_takeover);
|
||||
|
||||
m_listening = true;
|
||||
setup_notifier();
|
||||
return true;
|
||||
} else {
|
||||
if (rc != 0)
|
||||
perror("fstat");
|
||||
dbgln("It's not a socket, what the heck??");
|
||||
}
|
||||
}
|
||||
|
||||
dbgln("Failed to take the socket over from SystemServer");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalServer::setup_notifier()
|
||||
{
|
||||
m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
|
||||
m_notifier->on_ready_to_read = [this] {
|
||||
if (on_ready_to_accept)
|
||||
on_ready_to_accept();
|
||||
};
|
||||
}
|
||||
|
||||
bool LocalServer::listen(const String& address)
|
||||
{
|
||||
if (m_listening)
|
||||
return false;
|
||||
|
||||
int rc;
|
||||
|
||||
#ifdef SOCK_NONBLOCK
|
||||
m_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
#else
|
||||
m_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
int option = 1;
|
||||
ioctl(m_fd, FIONBIO, &option);
|
||||
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
ASSERT(m_fd >= 0);
|
||||
#ifndef __APPLE__
|
||||
rc = fchmod(m_fd, 0600);
|
||||
if (rc < 0) {
|
||||
perror("fchmod");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto socket_address = SocketAddress::local(address);
|
||||
auto un_optional = socket_address.to_sockaddr_un();
|
||||
if (!un_optional.has_value()) {
|
||||
perror("bind");
|
||||
return false;
|
||||
}
|
||||
auto un = un_optional.value();
|
||||
rc = ::bind(m_fd, (const sockaddr*)&un, sizeof(un));
|
||||
if (rc < 0) {
|
||||
perror("bind");
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = ::listen(m_fd, 5);
|
||||
if (rc < 0) {
|
||||
perror("listen");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_listening = true;
|
||||
setup_notifier();
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<LocalSocket> LocalServer::accept()
|
||||
{
|
||||
ASSERT(m_listening);
|
||||
sockaddr_un un;
|
||||
socklen_t un_size = sizeof(un);
|
||||
int accepted_fd = ::accept(m_fd, (sockaddr*)&un, &un_size);
|
||||
if (accepted_fd < 0) {
|
||||
perror("accept");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return LocalSocket::construct(accepted_fd);
|
||||
}
|
||||
|
||||
}
|
57
Userland/Libraries/LibCore/LocalServer.h
Normal file
57
Userland/Libraries/LibCore/LocalServer.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class LocalServer : public Object {
|
||||
C_OBJECT(LocalServer)
|
||||
public:
|
||||
virtual ~LocalServer() override;
|
||||
|
||||
bool take_over_from_system_server();
|
||||
bool is_listening() const { return m_listening; }
|
||||
bool listen(const String& address);
|
||||
|
||||
RefPtr<LocalSocket> accept();
|
||||
|
||||
Function<void()> on_ready_to_accept;
|
||||
|
||||
private:
|
||||
explicit LocalServer(Object* parent = nullptr);
|
||||
|
||||
void setup_notifier();
|
||||
|
||||
int m_fd { -1 };
|
||||
bool m_listening { false };
|
||||
RefPtr<Notifier> m_notifier;
|
||||
};
|
||||
|
||||
}
|
107
Userland/Libraries/LibCore/LocalSocket.cpp
Normal file
107
Userland/Libraries/LibCore/LocalSocket.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/LocalSocket.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
LocalSocket::LocalSocket(int fd, Object* parent)
|
||||
: Socket(Socket::Type::Local, parent)
|
||||
{
|
||||
// NOTE: This constructor is used by LocalServer::accept(), so the socket is already connected.
|
||||
m_connected = true;
|
||||
set_fd(fd);
|
||||
set_mode(IODevice::ReadWrite);
|
||||
set_error(0);
|
||||
}
|
||||
|
||||
LocalSocket::LocalSocket(Object* parent)
|
||||
: Socket(Socket::Type::Local, parent)
|
||||
{
|
||||
|
||||
#ifdef SOCK_NONBLOCK
|
||||
int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
#else
|
||||
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
int option = 1;
|
||||
ioctl(fd, FIONBIO, &option);
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
if (fd < 0) {
|
||||
set_error(errno);
|
||||
} else {
|
||||
set_fd(fd);
|
||||
set_mode(IODevice::ReadWrite);
|
||||
set_error(0);
|
||||
}
|
||||
}
|
||||
|
||||
LocalSocket::~LocalSocket()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<LocalSocket> LocalSocket::take_over_accepted_socket_from_system_server()
|
||||
{
|
||||
constexpr auto socket_takeover = "SOCKET_TAKEOVER";
|
||||
if (!getenv(socket_takeover))
|
||||
return nullptr;
|
||||
|
||||
// The SystemServer has passed us the socket as fd 3,
|
||||
// so use that instead of creating our own.
|
||||
constexpr int fd = 3;
|
||||
|
||||
// Sanity check: it has to be a socket.
|
||||
struct stat stat;
|
||||
int rc = fstat(fd, &stat);
|
||||
if (rc < 0 || !S_ISSOCK(stat.st_mode)) {
|
||||
if (rc != 0)
|
||||
perror("fstat");
|
||||
dbgln("ERROR: The fd we got from SystemServer is not a socket");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto socket = LocalSocket::construct(fd);
|
||||
|
||||
// It had to be !CLOEXEC for obvious reasons, but we
|
||||
// don't need it to be !CLOEXEC anymore, so set the
|
||||
// CLOEXEC flag now.
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
// We wouldn't want our children to think we're passing
|
||||
// them a socket either, so unset the env variable.
|
||||
unsetenv(socket_takeover);
|
||||
return socket;
|
||||
}
|
||||
|
||||
}
|
45
Userland/Libraries/LibCore/LocalSocket.h
Normal file
45
Userland/Libraries/LibCore/LocalSocket.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Socket.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class LocalSocket final : public Socket {
|
||||
C_OBJECT(LocalSocket)
|
||||
public:
|
||||
virtual ~LocalSocket() override;
|
||||
|
||||
static RefPtr<LocalSocket> take_over_accepted_socket_from_system_server();
|
||||
|
||||
private:
|
||||
explicit LocalSocket(Object* parent = nullptr);
|
||||
LocalSocket(int fd, Object* parent = nullptr);
|
||||
};
|
||||
|
||||
}
|
102
Userland/Libraries/LibCore/MimeData.cpp
Normal file
102
Userland/Libraries/LibCore/MimeData.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/MimeData.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Vector<String> MimeData::formats() const
|
||||
{
|
||||
Vector<String> mime_types;
|
||||
mime_types.ensure_capacity(m_data.size());
|
||||
for (auto it : m_data)
|
||||
mime_types.unchecked_append(it.key);
|
||||
return mime_types;
|
||||
}
|
||||
|
||||
Vector<URL> MimeData::urls() const
|
||||
{
|
||||
auto it = m_data.find("text/uri-list");
|
||||
if (it == m_data.end())
|
||||
return {};
|
||||
Vector<URL> urls;
|
||||
for (auto& line : StringView(it->value).split_view('\n')) {
|
||||
urls.append(URL(line));
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
void MimeData::set_urls(const Vector<URL>& urls)
|
||||
{
|
||||
StringBuilder builder;
|
||||
for (auto& url : urls) {
|
||||
builder.append(url.to_string());
|
||||
builder.append('\n');
|
||||
}
|
||||
set_data("text/uri-list", builder.to_byte_buffer());
|
||||
}
|
||||
|
||||
String MimeData::text() const
|
||||
{
|
||||
return String::copy(m_data.get("text/plain").value_or({}));
|
||||
}
|
||||
|
||||
void MimeData::set_text(const String& text)
|
||||
{
|
||||
set_data("text/plain", text.to_byte_buffer());
|
||||
}
|
||||
|
||||
String guess_mime_type_based_on_filename(const StringView& path)
|
||||
{
|
||||
if (path.ends_with(".pbm", CaseSensitivity::CaseInsensitive))
|
||||
return "image/x‑portable‑bitmap";
|
||||
if (path.ends_with(".pgm", CaseSensitivity::CaseInsensitive))
|
||||
return "image/x‑portable‑graymap";
|
||||
if (path.ends_with(".png", CaseSensitivity::CaseInsensitive))
|
||||
return "image/png";
|
||||
if (path.ends_with(".ppm", CaseSensitivity::CaseInsensitive))
|
||||
return "image/x‑portable‑pixmap";
|
||||
if (path.ends_with(".gif", CaseSensitivity::CaseInsensitive))
|
||||
return "image/gif";
|
||||
if (path.ends_with(".bmp", CaseSensitivity::CaseInsensitive))
|
||||
return "image/bmp";
|
||||
if (path.ends_with(".jpg", CaseSensitivity::CaseInsensitive) || path.ends_with(".jpeg", CaseSensitivity::CaseInsensitive))
|
||||
return "image/jpeg";
|
||||
if (path.ends_with(".svg", CaseSensitivity::CaseInsensitive))
|
||||
return "image/svg+xml";
|
||||
if (path.ends_with(".md", CaseSensitivity::CaseInsensitive))
|
||||
return "text/markdown";
|
||||
if (path.ends_with(".html", CaseSensitivity::CaseInsensitive) || path.ends_with(".htm", CaseSensitivity::CaseInsensitive))
|
||||
return "text/html";
|
||||
if (path.ends_with("/", CaseSensitivity::CaseInsensitive))
|
||||
return "text/html";
|
||||
if (path.ends_with(".csv", CaseSensitivity::CaseInsensitive))
|
||||
return "text/csv";
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
}
|
72
Userland/Libraries/LibCore/MimeData.h
Normal file
72
Userland/Libraries/LibCore/MimeData.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/URL.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class MimeData : public Object {
|
||||
C_OBJECT(MimeData);
|
||||
|
||||
public:
|
||||
virtual ~MimeData() { }
|
||||
|
||||
ByteBuffer data(const String& mime_type) const { return m_data.get(mime_type).value_or({}); }
|
||||
void set_data(const String& mime_type, const ByteBuffer& data) { m_data.set(mime_type, data); }
|
||||
|
||||
bool has_format(const String& mime_type) const { return m_data.contains(mime_type); }
|
||||
Vector<String> formats() const;
|
||||
|
||||
// Convenience helpers for "text/plain"
|
||||
bool has_text() const { return has_format("text/plain"); }
|
||||
String text() const;
|
||||
void set_text(const String&);
|
||||
|
||||
// Convenience helpers for "text/uri-list"
|
||||
bool has_urls() const { return has_format("text/uri-list"); }
|
||||
Vector<URL> urls() const;
|
||||
void set_urls(const Vector<URL>&);
|
||||
|
||||
const HashMap<String, ByteBuffer>& all_data() const { return m_data; }
|
||||
|
||||
private:
|
||||
MimeData() { }
|
||||
explicit MimeData(const HashMap<String, ByteBuffer>& data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
HashMap<String, ByteBuffer> m_data;
|
||||
};
|
||||
|
||||
String guess_mime_type_based_on_filename(const StringView&);
|
||||
|
||||
}
|
108
Userland/Libraries/LibCore/NetworkJob.cpp
Normal file
108
Userland/Libraries/LibCore/NetworkJob.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/NetworkJob.h>
|
||||
#include <LibCore/NetworkResponse.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//#define CNETWORKJOB_DEBUG
|
||||
|
||||
namespace Core {
|
||||
|
||||
NetworkJob::NetworkJob(OutputStream& output_stream)
|
||||
: m_output_stream(output_stream)
|
||||
{
|
||||
}
|
||||
|
||||
NetworkJob::~NetworkJob()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkJob::start()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkJob::shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkJob::did_finish(NonnullRefPtr<NetworkResponse>&& response)
|
||||
{
|
||||
// NOTE: We protect ourselves here, since the on_finish callback may otherwise
|
||||
// trigger destruction of this job somehow.
|
||||
NonnullRefPtr<NetworkJob> protector(*this);
|
||||
|
||||
m_response = move(response);
|
||||
#ifdef CNETWORKJOB_DEBUG
|
||||
dbg() << *this << " job did_finish!";
|
||||
#endif
|
||||
ASSERT(on_finish);
|
||||
on_finish(true);
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void NetworkJob::did_fail(Error error)
|
||||
{
|
||||
// NOTE: We protect ourselves here, since the on_finish callback may otherwise
|
||||
// trigger destruction of this job somehow.
|
||||
NonnullRefPtr<NetworkJob> protector(*this);
|
||||
|
||||
m_error = error;
|
||||
#ifdef CNETWORKJOB_DEBUG
|
||||
dbgprintf("%s{%p} job did_fail! error: %u (%s)\n", class_name(), this, (unsigned)error, to_string(error));
|
||||
#endif
|
||||
ASSERT(on_finish);
|
||||
on_finish(false);
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void NetworkJob::did_progress(Optional<u32> total_size, u32 downloaded)
|
||||
{
|
||||
// NOTE: We protect ourselves here, since the callback may otherwise
|
||||
// trigger destruction of this job somehow.
|
||||
NonnullRefPtr<NetworkJob> protector(*this);
|
||||
|
||||
if (on_progress)
|
||||
on_progress(total_size, downloaded);
|
||||
}
|
||||
|
||||
const char* to_string(NetworkJob::Error error)
|
||||
{
|
||||
switch (error) {
|
||||
case NetworkJob::Error::ProtocolFailed:
|
||||
return "ProtocolFailed";
|
||||
case NetworkJob::Error::ConnectionFailed:
|
||||
return "ConnectionFailed";
|
||||
case NetworkJob::Error::TransmissionFailed:
|
||||
return "TransmissionFailed";
|
||||
case NetworkJob::Error::Cancelled:
|
||||
return "Cancelled";
|
||||
default:
|
||||
return "(Unknown error)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
83
Userland/Libraries/LibCore/NetworkJob.h
Normal file
83
Userland/Libraries/LibCore/NetworkJob.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class NetworkJob : public Object {
|
||||
C_OBJECT_ABSTRACT(NetworkJob)
|
||||
public:
|
||||
enum class Error {
|
||||
None,
|
||||
ConnectionFailed,
|
||||
TransmissionFailed,
|
||||
ProtocolFailed,
|
||||
Cancelled,
|
||||
};
|
||||
virtual ~NetworkJob() override;
|
||||
|
||||
// Could fire twice, after Headers and after Trailers!
|
||||
Function<void(const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code)> on_headers_received;
|
||||
Function<void(bool success)> on_finish;
|
||||
Function<void(Optional<u32>, u32)> on_progress;
|
||||
|
||||
bool is_cancelled() const { return m_error == Error::Cancelled; }
|
||||
bool has_error() const { return m_error != Error::None; }
|
||||
Error error() const { return m_error; }
|
||||
NetworkResponse* response() { return m_response.ptr(); }
|
||||
const NetworkResponse* response() const { return m_response.ptr(); }
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
void cancel()
|
||||
{
|
||||
shutdown();
|
||||
m_error = Error::Cancelled;
|
||||
}
|
||||
|
||||
protected:
|
||||
NetworkJob(OutputStream&);
|
||||
void did_finish(NonnullRefPtr<NetworkResponse>&&);
|
||||
void did_fail(Error);
|
||||
void did_progress(Optional<u32> total_size, u32 downloaded);
|
||||
|
||||
size_t do_write(ReadonlyBytes bytes) { return m_output_stream.write(bytes); }
|
||||
|
||||
private:
|
||||
RefPtr<NetworkResponse> m_response;
|
||||
OutputStream& m_output_stream;
|
||||
Error m_error { Error::None };
|
||||
};
|
||||
|
||||
const char* to_string(NetworkJob::Error);
|
||||
|
||||
}
|
39
Userland/Libraries/LibCore/NetworkResponse.cpp
Normal file
39
Userland/Libraries/LibCore/NetworkResponse.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/NetworkResponse.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
NetworkResponse::NetworkResponse()
|
||||
{
|
||||
}
|
||||
|
||||
NetworkResponse::~NetworkResponse()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
46
Userland/Libraries/LibCore/NetworkResponse.h
Normal file
46
Userland/Libraries/LibCore/NetworkResponse.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/RefCounted.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class NetworkResponse : public RefCounted<NetworkResponse> {
|
||||
public:
|
||||
virtual ~NetworkResponse();
|
||||
|
||||
bool is_error() const { return m_error; }
|
||||
|
||||
protected:
|
||||
explicit NetworkResponse();
|
||||
|
||||
bool m_error { false };
|
||||
};
|
||||
|
||||
}
|
76
Userland/Libraries/LibCore/Notifier.cpp
Normal file
76
Userland/Libraries/LibCore/Notifier.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Notifier::Notifier(int fd, unsigned event_mask, Object* parent)
|
||||
: Object(parent)
|
||||
, m_fd(fd)
|
||||
, m_event_mask(event_mask)
|
||||
{
|
||||
set_enabled(true);
|
||||
}
|
||||
|
||||
Notifier::~Notifier()
|
||||
{
|
||||
set_enabled(false);
|
||||
}
|
||||
|
||||
void Notifier::set_enabled(bool enabled)
|
||||
{
|
||||
if (m_fd < 0)
|
||||
return;
|
||||
if (enabled)
|
||||
Core::EventLoop::register_notifier({}, *this);
|
||||
else
|
||||
Core::EventLoop::unregister_notifier({}, *this);
|
||||
}
|
||||
|
||||
void Notifier::close()
|
||||
{
|
||||
if (m_fd < 0)
|
||||
return;
|
||||
set_enabled(false);
|
||||
m_fd = -1;
|
||||
}
|
||||
|
||||
void Notifier::event(Core::Event& event)
|
||||
{
|
||||
if (event.type() == Core::Event::NotifierRead && on_ready_to_read) {
|
||||
on_ready_to_read();
|
||||
} else if (event.type() == Core::Event::NotifierWrite && on_ready_to_write) {
|
||||
on_ready_to_write();
|
||||
} else {
|
||||
Object::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
66
Userland/Libraries/LibCore/Notifier.h
Normal file
66
Userland/Libraries/LibCore/Notifier.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Notifier : public Object {
|
||||
C_OBJECT(Notifier)
|
||||
public:
|
||||
enum Event {
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Exceptional = 4,
|
||||
};
|
||||
|
||||
virtual ~Notifier() override;
|
||||
|
||||
void set_enabled(bool);
|
||||
|
||||
Function<void()> on_ready_to_read;
|
||||
Function<void()> on_ready_to_write;
|
||||
|
||||
void close();
|
||||
|
||||
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; }
|
||||
|
||||
void event(Core::Event&) override;
|
||||
|
||||
private:
|
||||
Notifier(int fd, unsigned event_mask, Object* parent = nullptr);
|
||||
|
||||
int m_fd { -1 };
|
||||
unsigned m_event_mask { 0 };
|
||||
};
|
||||
|
||||
}
|
270
Userland/Libraries/LibCore/Object.cpp
Normal file
270
Userland/Libraries/LibCore/Object.cpp
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
IntrusiveList<Object, &Object::m_all_objects_list_node>& Object::all_objects()
|
||||
{
|
||||
static IntrusiveList<Object, &Object::m_all_objects_list_node> objects;
|
||||
return objects;
|
||||
}
|
||||
|
||||
Object::Object(Object* parent)
|
||||
: m_parent(parent)
|
||||
{
|
||||
all_objects().append(*this);
|
||||
if (m_parent)
|
||||
m_parent->add_child(*this);
|
||||
|
||||
REGISTER_READONLY_STRING_PROPERTY("class_name", class_name);
|
||||
REGISTER_STRING_PROPERTY("name", name, set_name);
|
||||
|
||||
register_property(
|
||||
"address", [this] { return FlatPtr(this); },
|
||||
[](auto&) { return false; });
|
||||
|
||||
register_property(
|
||||
"parent", [this] { return FlatPtr(this->parent()); },
|
||||
[](auto&) { return false; });
|
||||
}
|
||||
|
||||
Object::~Object()
|
||||
{
|
||||
// NOTE: We move our children out to a stack vector to prevent other
|
||||
// code from trying to iterate over them.
|
||||
auto children = move(m_children);
|
||||
// NOTE: We also unparent the children, so that they won't try to unparent
|
||||
// themselves in their own destructors.
|
||||
for (auto& child : children)
|
||||
child.m_parent = nullptr;
|
||||
|
||||
all_objects().remove(*this);
|
||||
stop_timer();
|
||||
if (m_parent)
|
||||
m_parent->remove_child(*this);
|
||||
}
|
||||
|
||||
void Object::event(Core::Event& event)
|
||||
{
|
||||
switch (event.type()) {
|
||||
case Core::Event::Timer:
|
||||
return timer_event(static_cast<TimerEvent&>(event));
|
||||
case Core::Event::ChildAdded:
|
||||
case Core::Event::ChildRemoved:
|
||||
return child_event(static_cast<ChildEvent&>(event));
|
||||
case Core::Event::Invalid:
|
||||
ASSERT_NOT_REACHED();
|
||||
break;
|
||||
case Core::Event::Custom:
|
||||
return custom_event(static_cast<CustomEvent&>(event));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Object::add_child(Object& object)
|
||||
{
|
||||
// FIXME: Should we support reparenting objects?
|
||||
ASSERT(!object.parent() || object.parent() == this);
|
||||
object.m_parent = this;
|
||||
m_children.append(object);
|
||||
Core::ChildEvent child_event(Core::Event::ChildAdded, object);
|
||||
event(child_event);
|
||||
}
|
||||
|
||||
void Object::insert_child_before(Object& new_child, Object& before_child)
|
||||
{
|
||||
// FIXME: Should we support reparenting objects?
|
||||
ASSERT(!new_child.parent() || new_child.parent() == this);
|
||||
new_child.m_parent = this;
|
||||
m_children.insert_before_matching(new_child, [&](auto& existing_child) { return existing_child.ptr() == &before_child; });
|
||||
Core::ChildEvent child_event(Core::Event::ChildAdded, new_child, &before_child);
|
||||
event(child_event);
|
||||
}
|
||||
|
||||
void Object::remove_child(Object& object)
|
||||
{
|
||||
for (size_t i = 0; i < m_children.size(); ++i) {
|
||||
if (m_children.ptr_at(i).ptr() == &object) {
|
||||
// NOTE: We protect the child so it survives the handling of ChildRemoved.
|
||||
NonnullRefPtr<Object> protector = object;
|
||||
object.m_parent = nullptr;
|
||||
m_children.remove(i);
|
||||
Core::ChildEvent child_event(Core::Event::ChildRemoved, object);
|
||||
event(child_event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Object::remove_all_children()
|
||||
{
|
||||
while (!m_children.is_empty())
|
||||
m_children.first().remove_from_parent();
|
||||
}
|
||||
|
||||
void Object::timer_event(Core::TimerEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void Object::child_event(Core::ChildEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void Object::custom_event(CustomEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void Object::start_timer(int ms, TimerShouldFireWhenNotVisible fire_when_not_visible)
|
||||
{
|
||||
if (m_timer_id) {
|
||||
dbgln("{} {:p} already has a timer!", class_name(), this);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
m_timer_id = Core::EventLoop::register_timer(*this, ms, true, fire_when_not_visible);
|
||||
}
|
||||
|
||||
void Object::stop_timer()
|
||||
{
|
||||
if (!m_timer_id)
|
||||
return;
|
||||
bool success = Core::EventLoop::unregister_timer(m_timer_id);
|
||||
ASSERT(success);
|
||||
m_timer_id = 0;
|
||||
}
|
||||
|
||||
void Object::dump_tree(int indent)
|
||||
{
|
||||
for (int i = 0; i < indent; ++i) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("%s{%p}", class_name(), this);
|
||||
if (!name().is_null())
|
||||
printf(" %s", name().characters());
|
||||
printf("\n");
|
||||
|
||||
for_each_child([&](auto& child) {
|
||||
child.dump_tree(indent + 2);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void Object::deferred_invoke(Function<void(Object&)> invokee)
|
||||
{
|
||||
Core::EventLoop::current().post_event(*this, make<Core::DeferredInvocationEvent>(move(invokee)));
|
||||
}
|
||||
|
||||
void Object::save_to(JsonObject& json)
|
||||
{
|
||||
for (auto& it : m_properties) {
|
||||
auto& property = it.value;
|
||||
json.set(property->name(), property->get());
|
||||
}
|
||||
}
|
||||
|
||||
JsonValue Object::property(const StringView& name) const
|
||||
{
|
||||
auto it = m_properties.find(name);
|
||||
if (it == m_properties.end())
|
||||
return JsonValue();
|
||||
return it->value->get();
|
||||
}
|
||||
|
||||
bool Object::set_property(const StringView& name, const JsonValue& value)
|
||||
{
|
||||
auto it = m_properties.find(name);
|
||||
if (it == m_properties.end())
|
||||
return false;
|
||||
return it->value->set(value);
|
||||
}
|
||||
|
||||
bool Object::is_ancestor_of(const Object& other) const
|
||||
{
|
||||
if (&other == this)
|
||||
return false;
|
||||
for (auto* ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) {
|
||||
if (ancestor == this)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Object::dispatch_event(Core::Event& e, Object* stay_within)
|
||||
{
|
||||
ASSERT(!stay_within || stay_within == this || stay_within->is_ancestor_of(*this));
|
||||
auto* target = this;
|
||||
do {
|
||||
target->event(e);
|
||||
target = target->parent();
|
||||
if (target == stay_within) {
|
||||
// Prevent the event from bubbling any further.
|
||||
return;
|
||||
}
|
||||
} while (target && !e.is_accepted());
|
||||
}
|
||||
|
||||
bool Object::is_visible_for_timer_purposes() const
|
||||
{
|
||||
if (parent())
|
||||
return parent()->is_visible_for_timer_purposes();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Object::increment_inspector_count(Badge<RPCClient>)
|
||||
{
|
||||
++m_inspector_count;
|
||||
if (m_inspector_count == 1)
|
||||
did_begin_inspection();
|
||||
}
|
||||
|
||||
void Object::decrement_inspector_count(Badge<RPCClient>)
|
||||
{
|
||||
--m_inspector_count;
|
||||
if (!m_inspector_count)
|
||||
did_end_inspection();
|
||||
}
|
||||
|
||||
void Object::register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
|
||||
{
|
||||
m_properties.set(name, make<Property>(name, move(getter), move(setter)));
|
||||
}
|
||||
|
||||
const LogStream& operator<<(const LogStream& stream, const Object& object)
|
||||
{
|
||||
return stream << object.class_name() << '{' << &object << '}';
|
||||
}
|
||||
|
||||
}
|
346
Userland/Libraries/LibCore/Object.h
Normal file
346
Userland/Libraries/LibCore/Object.h
Normal file
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibCore/Property.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class RPCClient;
|
||||
|
||||
enum class TimerShouldFireWhenNotVisible {
|
||||
No = 0,
|
||||
Yes
|
||||
};
|
||||
|
||||
#define C_OBJECT(klass) \
|
||||
public: \
|
||||
virtual const char* class_name() const override { return #klass; } \
|
||||
template<class... Args> \
|
||||
static inline NonnullRefPtr<klass> construct(Args&&... args) \
|
||||
{ \
|
||||
return adopt(*new klass(forward<Args>(args)...)); \
|
||||
}
|
||||
|
||||
#define C_OBJECT_ABSTRACT(klass) \
|
||||
public: \
|
||||
virtual const char* class_name() const override { return #klass; }
|
||||
|
||||
class Object
|
||||
: public RefCounted<Object>
|
||||
, public Weakable<Object> {
|
||||
// NOTE: No C_OBJECT macro for Core::Object itself.
|
||||
|
||||
AK_MAKE_NONCOPYABLE(Object);
|
||||
AK_MAKE_NONMOVABLE(Object);
|
||||
|
||||
IntrusiveListNode m_all_objects_list_node;
|
||||
|
||||
public:
|
||||
virtual ~Object();
|
||||
|
||||
virtual const char* class_name() const = 0;
|
||||
virtual void event(Core::Event&);
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
void set_name(const StringView& name) { m_name = name; }
|
||||
|
||||
NonnullRefPtrVector<Object>& children() { return m_children; }
|
||||
const NonnullRefPtrVector<Object>& 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) requires IsBaseOf<Object, T>::value;
|
||||
|
||||
template<typename T>
|
||||
T* find_child_of_type_named(const String&) requires IsBaseOf<Object, T>::value;
|
||||
|
||||
template<typename T>
|
||||
T* find_descendant_of_type_named(const String&) requires IsBaseOf<Object, T>::value;
|
||||
|
||||
bool is_ancestor_of(const Object&) const;
|
||||
|
||||
Object* parent() { return m_parent; }
|
||||
const Object* parent() const { return m_parent; }
|
||||
|
||||
void start_timer(int ms, TimerShouldFireWhenNotVisible = TimerShouldFireWhenNotVisible::No);
|
||||
void stop_timer();
|
||||
bool has_timer() const { return m_timer_id; }
|
||||
|
||||
void add_child(Object&);
|
||||
void insert_child_before(Object& new_child, Object& before_child);
|
||||
void remove_child(Object&);
|
||||
void remove_all_children();
|
||||
|
||||
void dump_tree(int indent = 0);
|
||||
|
||||
void deferred_invoke(Function<void(Object&)>);
|
||||
|
||||
void save_to(AK::JsonObject&);
|
||||
|
||||
bool set_property(const StringView& name, const JsonValue& value);
|
||||
JsonValue property(const StringView& name) const;
|
||||
const HashMap<String, NonnullOwnPtr<Property>>& properties() const { return m_properties; }
|
||||
|
||||
static IntrusiveList<Object, &Object::m_all_objects_list_node>& all_objects();
|
||||
|
||||
void dispatch_event(Core::Event&, Object* stay_within = nullptr);
|
||||
|
||||
void remove_from_parent()
|
||||
{
|
||||
if (m_parent)
|
||||
m_parent->remove_child(*this);
|
||||
}
|
||||
|
||||
template<class T, class... Args>
|
||||
inline T& add(Args&&... args)
|
||||
{
|
||||
auto child = T::construct(forward<Args>(args)...);
|
||||
add_child(*child);
|
||||
return child;
|
||||
}
|
||||
|
||||
virtual bool is_visible_for_timer_purposes() const;
|
||||
|
||||
bool is_being_inspected() const { return m_inspector_count; }
|
||||
|
||||
void increment_inspector_count(Badge<RPCClient>);
|
||||
void decrement_inspector_count(Badge<RPCClient>);
|
||||
|
||||
protected:
|
||||
explicit Object(Object* parent = nullptr);
|
||||
|
||||
void register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
|
||||
|
||||
virtual void timer_event(TimerEvent&);
|
||||
virtual void custom_event(CustomEvent&);
|
||||
|
||||
// NOTE: You may get child events for children that are not yet fully constructed!
|
||||
virtual void child_event(ChildEvent&);
|
||||
|
||||
virtual void did_begin_inspection() { }
|
||||
virtual void did_end_inspection() { }
|
||||
|
||||
private:
|
||||
Object* m_parent { nullptr };
|
||||
String m_name;
|
||||
int m_timer_id { 0 };
|
||||
unsigned m_inspector_count { 0 };
|
||||
HashMap<String, NonnullOwnPtr<Property>> m_properties;
|
||||
NonnullRefPtrVector<Object> m_children;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Core::Object> : AK::Formatter<FormatString> {
|
||||
void format(FormatBuilder& builder, const Core::Object& value)
|
||||
{
|
||||
return AK::Formatter<FormatString>::format(builder, "{}({})", value.class_name(), &value);
|
||||
}
|
||||
};
|
||||
|
||||
namespace Core {
|
||||
template<typename T, typename Callback>
|
||||
inline void Object::for_each_child_of_type(Callback callback) requires IsBaseOf<Object, T>::value
|
||||
{
|
||||
for_each_child([&](auto& child) {
|
||||
if (auto* child_as_t = dynamic_cast<T*>(&child); child_as_t)
|
||||
return callback(*child_as_t);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Object::find_child_of_type_named(const String& name) requires IsBaseOf<Object, T>::value
|
||||
{
|
||||
T* found_child = nullptr;
|
||||
for_each_child_of_type<T>([&](auto& child) {
|
||||
if (child.name() == name) {
|
||||
found_child = &child;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return found_child;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Object::find_descendant_of_type_named(const String& name) requires IsBaseOf<Object, T>::value
|
||||
{
|
||||
auto* this_as_t = dynamic_cast<T*>(this);
|
||||
if (this_as_t && this->name() == name)
|
||||
return this_as_t;
|
||||
T* found_child = nullptr;
|
||||
for_each_child([&](auto& child) {
|
||||
found_child = child.template find_descendant_of_type_named<T>(name);
|
||||
if (found_child)
|
||||
return IterationDecision::Break;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return found_child;
|
||||
}
|
||||
|
||||
const LogStream& operator<<(const LogStream&, const Object&);
|
||||
|
||||
#define REGISTER_INT_PROPERTY(property_name, getter, setter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { return this->getter(); }, \
|
||||
[this](auto& value) { \
|
||||
this->setter(value.template to_number<int>()); \
|
||||
return true; \
|
||||
});
|
||||
|
||||
#define REGISTER_BOOL_PROPERTY(property_name, getter, setter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { return this->getter(); }, \
|
||||
[this](auto& value) { \
|
||||
this->setter(value.to_bool()); \
|
||||
return true; \
|
||||
});
|
||||
|
||||
#define REGISTER_STRING_PROPERTY(property_name, getter, setter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { return this->getter(); }, \
|
||||
[this](auto& value) { \
|
||||
this->setter(value.to_string()); \
|
||||
return true; \
|
||||
});
|
||||
|
||||
#define REGISTER_READONLY_STRING_PROPERTY(property_name, getter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { return this->getter(); }, \
|
||||
{});
|
||||
|
||||
#define REGISTER_RECT_PROPERTY(property_name, getter, setter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { \
|
||||
auto rect = this->getter(); \
|
||||
JsonObject rect_object; \
|
||||
rect_object.set("x", rect.x()); \
|
||||
rect_object.set("y", rect.y()); \
|
||||
rect_object.set("width", rect.width()); \
|
||||
rect_object.set("height", rect.height()); \
|
||||
return rect_object; \
|
||||
}, \
|
||||
[this](auto& value) { \
|
||||
if (!value.is_object()) \
|
||||
return false; \
|
||||
Gfx::IntRect rect; \
|
||||
rect.set_x(value.as_object().get("x").to_i32()); \
|
||||
rect.set_y(value.as_object().get("y").to_i32()); \
|
||||
rect.set_width(value.as_object().get("width").to_i32()); \
|
||||
rect.set_height(value.as_object().get("height").to_i32()); \
|
||||
setter(rect); \
|
||||
return true; \
|
||||
});
|
||||
|
||||
#define REGISTER_SIZE_PROPERTY(property_name, getter, setter) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this] { \
|
||||
auto size = this->getter(); \
|
||||
JsonObject size_object; \
|
||||
size_object.set("width", size.width()); \
|
||||
size_object.set("height", size.height()); \
|
||||
return size_object; \
|
||||
}, \
|
||||
[this](auto& value) { \
|
||||
if (!value.is_object()) \
|
||||
return false; \
|
||||
Gfx::IntSize size; \
|
||||
size.set_width(value.as_object().get("width").to_i32()); \
|
||||
size.set_height(value.as_object().get("height").to_i32()); \
|
||||
setter(size); \
|
||||
return true; \
|
||||
});
|
||||
|
||||
#define REGISTER_ENUM_PROPERTY(property_name, getter, setter, EnumType, ...) \
|
||||
register_property( \
|
||||
property_name, \
|
||||
[this]() -> JsonValue { \
|
||||
struct { \
|
||||
EnumType enum_value; \
|
||||
String string_value; \
|
||||
} options[] = { __VA_ARGS__ }; \
|
||||
auto enum_value = getter(); \
|
||||
for (size_t i = 0; i < array_size(options); ++i) { \
|
||||
auto& option = options[i]; \
|
||||
if (enum_value == option.enum_value) \
|
||||
return option.string_value; \
|
||||
} \
|
||||
return JsonValue(); \
|
||||
}, \
|
||||
[this](auto& value) { \
|
||||
struct { \
|
||||
EnumType enum_value; \
|
||||
String string_value; \
|
||||
} options[] = { __VA_ARGS__ }; \
|
||||
auto string_value = value.as_string(); \
|
||||
for (size_t i = 0; i < array_size(options); ++i) { \
|
||||
auto& option = options[i]; \
|
||||
if (string_value == option.string_value) { \
|
||||
setter(option.enum_value); \
|
||||
return true; \
|
||||
} \
|
||||
} \
|
||||
return false; \
|
||||
})
|
||||
|
||||
#define REGISTER_TEXT_ALIGNMENT_PROPERTY(property_name, getter, setter) \
|
||||
REGISTER_ENUM_PROPERTY( \
|
||||
property_name, getter, setter, Gfx::TextAlignment, \
|
||||
{ Gfx::TextAlignment::TopLeft, "TopLeft" }, \
|
||||
{ Gfx::TextAlignment::CenterLeft, "CenterLeft" }, \
|
||||
{ Gfx::TextAlignment::Center, "Center" }, \
|
||||
{ Gfx::TextAlignment::CenterRight, "CenterRight" }, \
|
||||
{ Gfx::TextAlignment::TopRight, "TopRight" }, \
|
||||
{ Gfx::TextAlignment::BottomRight, "BottomRight" })
|
||||
}
|
142
Userland/Libraries/LibCore/ProcessStatisticsReader.cpp
Normal file
142
Userland/Libraries/LibCore/ProcessStatisticsReader.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/ProcessStatisticsReader.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
HashMap<uid_t, String> ProcessStatisticsReader::s_usernames;
|
||||
|
||||
Optional<HashMap<pid_t, Core::ProcessStatistics>> ProcessStatisticsReader::get_all(RefPtr<Core::File>& proc_all_file)
|
||||
{
|
||||
if (proc_all_file) {
|
||||
if (!proc_all_file->seek(0, Core::File::SeekMode::SetPosition)) {
|
||||
fprintf(stderr, "ProcessStatisticsReader: Failed to refresh /proc/all: %s\n", proc_all_file->error_string());
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
proc_all_file = Core::File::construct("/proc/all");
|
||||
if (!proc_all_file->open(Core::IODevice::ReadOnly)) {
|
||||
fprintf(stderr, "ProcessStatisticsReader: Failed to open /proc/all: %s\n", proc_all_file->error_string());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<pid_t, Core::ProcessStatistics> map;
|
||||
|
||||
auto file_contents = proc_all_file->read_all();
|
||||
auto json = JsonValue::from_string(file_contents);
|
||||
if (!json.has_value())
|
||||
return {};
|
||||
json.value().as_array().for_each([&](auto& value) {
|
||||
const JsonObject& process_object = value.as_object();
|
||||
Core::ProcessStatistics process;
|
||||
|
||||
// kernel data first
|
||||
process.pid = process_object.get("pid").to_u32();
|
||||
process.pgid = process_object.get("pgid").to_u32();
|
||||
process.pgp = process_object.get("pgp").to_u32();
|
||||
process.sid = process_object.get("sid").to_u32();
|
||||
process.uid = process_object.get("uid").to_u32();
|
||||
process.gid = process_object.get("gid").to_u32();
|
||||
process.ppid = process_object.get("ppid").to_u32();
|
||||
process.nfds = process_object.get("nfds").to_u32();
|
||||
process.name = process_object.get("name").to_string();
|
||||
process.executable = process_object.get("executable").to_string();
|
||||
process.tty = process_object.get("tty").to_string();
|
||||
process.pledge = process_object.get("pledge").to_string();
|
||||
process.veil = process_object.get("veil").to_string();
|
||||
process.amount_virtual = process_object.get("amount_virtual").to_u32();
|
||||
process.amount_resident = process_object.get("amount_resident").to_u32();
|
||||
process.amount_shared = process_object.get("amount_shared").to_u32();
|
||||
process.amount_dirty_private = process_object.get("amount_dirty_private").to_u32();
|
||||
process.amount_clean_inode = process_object.get("amount_clean_inode").to_u32();
|
||||
process.amount_purgeable_volatile = process_object.get("amount_purgeable_volatile").to_u32();
|
||||
process.amount_purgeable_nonvolatile = process_object.get("amount_purgeable_nonvolatile").to_u32();
|
||||
|
||||
auto& thread_array = process_object.get_ptr("threads")->as_array();
|
||||
process.threads.ensure_capacity(thread_array.size());
|
||||
thread_array.for_each([&](auto& value) {
|
||||
auto& thread_object = value.as_object();
|
||||
Core::ThreadStatistics thread;
|
||||
thread.tid = thread_object.get("tid").to_u32();
|
||||
thread.times_scheduled = thread_object.get("times_scheduled").to_u32();
|
||||
thread.name = thread_object.get("name").to_string();
|
||||
thread.state = thread_object.get("state").to_string();
|
||||
thread.ticks_user = thread_object.get("ticks_user").to_u32();
|
||||
thread.ticks_kernel = thread_object.get("ticks_kernel").to_u32();
|
||||
thread.cpu = thread_object.get("cpu").to_u32();
|
||||
thread.priority = thread_object.get("priority").to_u32();
|
||||
thread.effective_priority = thread_object.get("effective_priority").to_u32();
|
||||
thread.syscall_count = thread_object.get("syscall_count").to_u32();
|
||||
thread.inode_faults = thread_object.get("inode_faults").to_u32();
|
||||
thread.zero_faults = thread_object.get("zero_faults").to_u32();
|
||||
thread.cow_faults = thread_object.get("cow_faults").to_u32();
|
||||
thread.unix_socket_read_bytes = thread_object.get("unix_socket_read_bytes").to_u32();
|
||||
thread.unix_socket_write_bytes = thread_object.get("unix_socket_write_bytes").to_u32();
|
||||
thread.ipv4_socket_read_bytes = thread_object.get("ipv4_socket_read_bytes").to_u32();
|
||||
thread.ipv4_socket_write_bytes = thread_object.get("ipv4_socket_write_bytes").to_u32();
|
||||
thread.file_read_bytes = thread_object.get("file_read_bytes").to_u32();
|
||||
thread.file_write_bytes = thread_object.get("file_write_bytes").to_u32();
|
||||
process.threads.append(move(thread));
|
||||
});
|
||||
|
||||
// and synthetic data last
|
||||
process.username = username_from_uid(process.uid);
|
||||
map.set(process.pid, process);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
Optional<HashMap<pid_t, Core::ProcessStatistics>> ProcessStatisticsReader::get_all()
|
||||
{
|
||||
RefPtr<Core::File> proc_all_file;
|
||||
return get_all(proc_all_file);
|
||||
}
|
||||
|
||||
String ProcessStatisticsReader::username_from_uid(uid_t uid)
|
||||
{
|
||||
if (s_usernames.is_empty()) {
|
||||
setpwent();
|
||||
while (auto* passwd = getpwent())
|
||||
s_usernames.set(passwd->pw_uid, passwd->pw_name);
|
||||
endpwent();
|
||||
}
|
||||
|
||||
auto it = s_usernames.find(uid);
|
||||
if (it != s_usernames.end())
|
||||
return (*it).value;
|
||||
return String::number(uid);
|
||||
}
|
||||
}
|
98
Userland/Libraries/LibCore/ProcessStatisticsReader.h
Normal file
98
Userland/Libraries/LibCore/ProcessStatisticsReader.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct ThreadStatistics {
|
||||
pid_t tid;
|
||||
unsigned times_scheduled;
|
||||
unsigned ticks_user;
|
||||
unsigned ticks_kernel;
|
||||
unsigned syscall_count;
|
||||
unsigned inode_faults;
|
||||
unsigned zero_faults;
|
||||
unsigned cow_faults;
|
||||
unsigned unix_socket_read_bytes;
|
||||
unsigned unix_socket_write_bytes;
|
||||
unsigned ipv4_socket_read_bytes;
|
||||
unsigned ipv4_socket_write_bytes;
|
||||
unsigned file_read_bytes;
|
||||
unsigned file_write_bytes;
|
||||
String state;
|
||||
u32 cpu;
|
||||
u32 priority;
|
||||
u32 effective_priority;
|
||||
String name;
|
||||
};
|
||||
|
||||
struct ProcessStatistics {
|
||||
// Keep this in sync with /proc/all.
|
||||
// From the kernel side:
|
||||
pid_t pid;
|
||||
pid_t pgid;
|
||||
pid_t pgp;
|
||||
pid_t sid;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t ppid;
|
||||
unsigned nfds;
|
||||
String name;
|
||||
String executable;
|
||||
String tty;
|
||||
String pledge;
|
||||
String veil;
|
||||
size_t amount_virtual;
|
||||
size_t amount_resident;
|
||||
size_t amount_shared;
|
||||
size_t amount_dirty_private;
|
||||
size_t amount_clean_inode;
|
||||
size_t amount_purgeable_volatile;
|
||||
size_t amount_purgeable_nonvolatile;
|
||||
|
||||
Vector<Core::ThreadStatistics> threads;
|
||||
|
||||
// synthetic
|
||||
String username;
|
||||
};
|
||||
|
||||
class ProcessStatisticsReader {
|
||||
public:
|
||||
static Optional<HashMap<pid_t, Core::ProcessStatistics>> get_all(RefPtr<Core::File>&);
|
||||
static Optional<HashMap<pid_t, Core::ProcessStatistics>> get_all();
|
||||
|
||||
private:
|
||||
static String username_from_uid(uid_t);
|
||||
static HashMap<uid_t, String> s_usernames;
|
||||
};
|
||||
|
||||
}
|
42
Userland/Libraries/LibCore/Property.cpp
Normal file
42
Userland/Libraries/LibCore/Property.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/Property.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Property::Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
|
||||
: m_name(move(name))
|
||||
, m_getter(move(getter))
|
||||
, m_setter(move(setter))
|
||||
{
|
||||
}
|
||||
|
||||
Property::~Property()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
59
Userland/Libraries/LibCore/Property.h
Normal file
59
Userland/Libraries/LibCore/Property.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/JsonValue.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Property {
|
||||
AK_MAKE_NONCOPYABLE(Property);
|
||||
|
||||
public:
|
||||
Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
|
||||
~Property();
|
||||
|
||||
bool set(const JsonValue& value)
|
||||
{
|
||||
if (!m_setter)
|
||||
return false;
|
||||
return m_setter(value);
|
||||
}
|
||||
|
||||
JsonValue get() const { return m_getter(); }
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
bool is_readonly() const { return !m_setter; }
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
Function<JsonValue()> m_getter;
|
||||
Function<bool(const JsonValue&)> m_setter;
|
||||
};
|
||||
|
||||
}
|
236
Userland/Libraries/LibCore/Socket.cpp
Normal file
236
Userland/Libraries/LibCore/Socket.cpp
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//#define CSOCKET_DEBUG
|
||||
|
||||
namespace Core {
|
||||
|
||||
Socket::Socket(Type type, Object* parent)
|
||||
: IODevice(parent)
|
||||
, m_type(type)
|
||||
{
|
||||
register_property(
|
||||
"source_address", [this] { return m_source_address.to_string(); },
|
||||
[](auto&) { return false; });
|
||||
|
||||
register_property(
|
||||
"destination_address", [this] { return m_destination_address.to_string(); },
|
||||
[](auto&) { return false; });
|
||||
|
||||
register_property(
|
||||
"source_port", [this] { return m_source_port; },
|
||||
[](auto&) { return false; });
|
||||
|
||||
register_property(
|
||||
"destination_port", [this] { return m_destination_port; },
|
||||
[](auto&) { return false; });
|
||||
|
||||
register_property(
|
||||
"connected", [this] { return m_connected; },
|
||||
[](auto&) { return false; });
|
||||
}
|
||||
|
||||
Socket::~Socket()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool Socket::connect(const String& hostname, int port)
|
||||
{
|
||||
auto* hostent = gethostbyname(hostname.characters());
|
||||
if (!hostent) {
|
||||
dbgln("Socket::connect: Unable to resolve '{}'", hostname);
|
||||
return false;
|
||||
}
|
||||
|
||||
IPv4Address host_address((const u8*)hostent->h_addr_list[0]);
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << "Socket::connect: Resolved '" << hostname << "' to " << host_address;
|
||||
#endif
|
||||
return connect(host_address, port);
|
||||
}
|
||||
|
||||
void Socket::set_blocking(bool blocking)
|
||||
{
|
||||
int flags = fcntl(fd(), F_GETFL, 0);
|
||||
ASSERT(flags >= 0);
|
||||
if (blocking)
|
||||
flags = fcntl(fd(), F_SETFL, flags & ~O_NONBLOCK);
|
||||
else
|
||||
flags = fcntl(fd(), F_SETFL, flags | O_NONBLOCK);
|
||||
ASSERT(flags == 0);
|
||||
}
|
||||
|
||||
bool Socket::connect(const SocketAddress& address, int port)
|
||||
{
|
||||
ASSERT(!is_connected());
|
||||
ASSERT(address.type() == SocketAddress::Type::IPv4);
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << *this << " connecting to " << address << "...";
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
return common_connect((struct sockaddr*)&addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool Socket::connect(const SocketAddress& address)
|
||||
{
|
||||
ASSERT(!is_connected());
|
||||
ASSERT(address.type() == SocketAddress::Type::Local);
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << *this << " connecting to " << address << "...";
|
||||
#endif
|
||||
|
||||
sockaddr_un saddr;
|
||||
saddr.sun_family = AF_LOCAL;
|
||||
auto dest_address = address.to_string();
|
||||
bool fits = dest_address.copy_characters_to_buffer(saddr.sun_path, sizeof(saddr.sun_path));
|
||||
if (!fits) {
|
||||
fprintf(stderr, "Core::Socket: Failed to connect() to %s: Path is too long!\n", dest_address.characters());
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
m_destination_address = address;
|
||||
|
||||
return common_connect((const sockaddr*)&saddr, sizeof(saddr));
|
||||
}
|
||||
|
||||
bool Socket::common_connect(const struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
auto connected = [this] {
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << *this << " connected!";
|
||||
#endif
|
||||
if (!m_connected) {
|
||||
m_connected = true;
|
||||
ensure_read_notifier();
|
||||
if (m_notifier) {
|
||||
m_notifier->remove_from_parent();
|
||||
m_notifier = nullptr;
|
||||
}
|
||||
if (on_connected)
|
||||
on_connected();
|
||||
}
|
||||
};
|
||||
int rc = ::connect(fd(), addr, addrlen);
|
||||
if (rc < 0) {
|
||||
if (errno == EINPROGRESS) {
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << *this << " connection in progress (EINPROGRESS)";
|
||||
#endif
|
||||
m_notifier = Notifier::construct(fd(), Notifier::Event::Write, this);
|
||||
m_notifier->on_ready_to_write = move(connected);
|
||||
return true;
|
||||
}
|
||||
int saved_errno = errno;
|
||||
fprintf(stderr, "Core::Socket: Failed to connect() to %s: %s\n", destination_address().to_string().characters(), strerror(saved_errno));
|
||||
errno = saved_errno;
|
||||
return false;
|
||||
}
|
||||
#ifdef CSOCKET_DEBUG
|
||||
dbg() << *this << " connected ok!";
|
||||
#endif
|
||||
connected();
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteBuffer Socket::receive(int max_size)
|
||||
{
|
||||
auto buffer = read(max_size);
|
||||
if (eof())
|
||||
m_connected = false;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool Socket::send(ReadonlyBytes data)
|
||||
{
|
||||
ssize_t nsent = ::send(fd(), data.data(), data.size(), 0);
|
||||
if (nsent < 0) {
|
||||
set_error(errno);
|
||||
return false;
|
||||
}
|
||||
ASSERT(static_cast<size_t>(nsent) == data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Socket::did_update_fd(int fd)
|
||||
{
|
||||
if (fd < 0) {
|
||||
if (m_read_notifier) {
|
||||
m_read_notifier->remove_from_parent();
|
||||
m_read_notifier = nullptr;
|
||||
}
|
||||
if (m_notifier) {
|
||||
m_notifier->remove_from_parent();
|
||||
m_notifier = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_connected) {
|
||||
ensure_read_notifier();
|
||||
} else {
|
||||
// I don't think it would be right if we updated the fd while not connected *but* while having a notifier..
|
||||
ASSERT(!m_read_notifier);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::ensure_read_notifier()
|
||||
{
|
||||
ASSERT(m_connected);
|
||||
m_read_notifier = Notifier::construct(fd(), Notifier::Event::Read, this);
|
||||
m_read_notifier->on_ready_to_read = [this] {
|
||||
if (!can_read())
|
||||
return;
|
||||
if (on_ready_to_read)
|
||||
on_ready_to_read();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
89
Userland/Libraries/LibCore/Socket.h
Normal file
89
Userland/Libraries/LibCore/Socket.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Span.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
#include <LibCore/SocketAddress.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Socket : public IODevice {
|
||||
C_OBJECT(Socket)
|
||||
public:
|
||||
enum class Type {
|
||||
Invalid,
|
||||
TCP,
|
||||
UDP,
|
||||
Local,
|
||||
};
|
||||
virtual ~Socket() override;
|
||||
|
||||
Type type() const { return m_type; }
|
||||
|
||||
virtual bool connect(const String& hostname, int port);
|
||||
bool connect(const SocketAddress&, int port);
|
||||
bool connect(const SocketAddress&);
|
||||
|
||||
ByteBuffer receive(int max_size);
|
||||
bool send(ReadonlyBytes);
|
||||
|
||||
bool is_connected() const { return m_connected; }
|
||||
void set_blocking(bool blocking);
|
||||
|
||||
SocketAddress source_address() const { return m_source_address; }
|
||||
int source_port() const { return m_source_port; }
|
||||
|
||||
SocketAddress destination_address() const { return m_destination_address; }
|
||||
int destination_port() const { return m_destination_port; }
|
||||
|
||||
Function<void()> on_connected;
|
||||
Function<void()> on_ready_to_read;
|
||||
|
||||
protected:
|
||||
Socket(Type, Object* parent);
|
||||
|
||||
SocketAddress m_source_address;
|
||||
SocketAddress m_destination_address;
|
||||
int m_source_port { -1 };
|
||||
int m_destination_port { -1 };
|
||||
bool m_connected { false };
|
||||
|
||||
virtual void did_update_fd(int) override;
|
||||
virtual bool common_connect(const struct sockaddr*, socklen_t);
|
||||
|
||||
private:
|
||||
virtual bool open(IODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
|
||||
void ensure_read_notifier();
|
||||
|
||||
Type m_type { Type::Invalid };
|
||||
RefPtr<Notifier> m_notifier;
|
||||
RefPtr<Notifier> m_read_notifier;
|
||||
};
|
||||
|
||||
}
|
36
Userland/Libraries/LibCore/SocketAddress.cpp
Normal file
36
Userland/Libraries/LibCore/SocketAddress.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/SocketAddress.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
const LogStream& operator<<(const LogStream& stream, const SocketAddress& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
||||
|
||||
}
|
115
Userland/Libraries/LibCore/SocketAddress.h
Normal file
115
Userland/Libraries/LibCore/SocketAddress.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class SocketAddress {
|
||||
public:
|
||||
enum class Type {
|
||||
Invalid,
|
||||
IPv4,
|
||||
Local
|
||||
};
|
||||
|
||||
SocketAddress() { }
|
||||
SocketAddress(const IPv4Address& address)
|
||||
: m_type(Type::IPv4)
|
||||
, m_ipv4_address(address)
|
||||
{
|
||||
}
|
||||
|
||||
SocketAddress(const IPv4Address& address, u16 port)
|
||||
: m_type(Type::IPv4)
|
||||
, m_ipv4_address(address)
|
||||
, m_port(port)
|
||||
{
|
||||
}
|
||||
|
||||
static SocketAddress local(const String& address)
|
||||
{
|
||||
SocketAddress addr;
|
||||
addr.m_type = Type::Local;
|
||||
addr.m_local_address = address;
|
||||
return addr;
|
||||
}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
bool is_valid() const { return m_type != Type::Invalid; }
|
||||
IPv4Address ipv4_address() const { return m_ipv4_address; }
|
||||
u16 port() const { return m_port; }
|
||||
|
||||
String to_string() const
|
||||
{
|
||||
switch (m_type) {
|
||||
case Type::IPv4:
|
||||
return String::format("%s:%d", m_ipv4_address.to_string().characters(), m_port);
|
||||
case Type::Local:
|
||||
return m_local_address;
|
||||
default:
|
||||
return "[SocketAddress]";
|
||||
}
|
||||
}
|
||||
|
||||
Optional<sockaddr_un> to_sockaddr_un() const
|
||||
{
|
||||
ASSERT(type() == Type::Local);
|
||||
sockaddr_un address;
|
||||
address.sun_family = AF_LOCAL;
|
||||
bool fits = m_local_address.copy_characters_to_buffer(address.sun_path, sizeof(address.sun_path));
|
||||
if (!fits)
|
||||
return {};
|
||||
return address;
|
||||
}
|
||||
|
||||
sockaddr_in to_sockaddr_in() const
|
||||
{
|
||||
ASSERT(type() == Type::IPv4);
|
||||
sockaddr_in address {};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = m_ipv4_address.to_in_addr_t();
|
||||
address.sin_port = htons(m_port);
|
||||
return address;
|
||||
}
|
||||
|
||||
private:
|
||||
Type m_type { Type::Invalid };
|
||||
IPv4Address m_ipv4_address;
|
||||
u16 m_port { 0 };
|
||||
String m_local_address;
|
||||
};
|
||||
|
||||
const LogStream& operator<<(const LogStream&, const SocketAddress&);
|
||||
|
||||
}
|
77
Userland/Libraries/LibCore/StandardPaths.cpp
Normal file
77
Userland/Libraries/LibCore/StandardPaths.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
String StandardPaths::home_directory()
|
||||
{
|
||||
if (auto* home_env = getenv("HOME"))
|
||||
return LexicalPath::canonicalized_path(home_env);
|
||||
|
||||
auto* pwd = getpwuid(getuid());
|
||||
String path = pwd ? pwd->pw_dir : "/";
|
||||
endpwent();
|
||||
return LexicalPath::canonicalized_path(path);
|
||||
}
|
||||
|
||||
String StandardPaths::desktop_directory()
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(home_directory());
|
||||
builder.append("/Desktop");
|
||||
return LexicalPath::canonicalized_path(builder.to_string());
|
||||
}
|
||||
|
||||
String StandardPaths::downloads_directory()
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(home_directory());
|
||||
builder.append("/Downloads");
|
||||
return LexicalPath::canonicalized_path(builder.to_string());
|
||||
}
|
||||
|
||||
String StandardPaths::config_directory()
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(home_directory());
|
||||
builder.append("/.config");
|
||||
return LexicalPath::canonicalized_path(builder.to_string());
|
||||
}
|
||||
|
||||
String StandardPaths::tempfile_directory()
|
||||
{
|
||||
return "/tmp";
|
||||
}
|
||||
|
||||
}
|
42
Userland/Libraries/LibCore/StandardPaths.h
Normal file
42
Userland/Libraries/LibCore/StandardPaths.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class StandardPaths {
|
||||
public:
|
||||
static String home_directory();
|
||||
static String desktop_directory();
|
||||
static String downloads_directory();
|
||||
static String tempfile_directory();
|
||||
static String config_directory();
|
||||
};
|
||||
|
||||
}
|
56
Userland/Libraries/LibCore/SyscallUtils.h
Normal file
56
Userland/Libraries/LibCore/SyscallUtils.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
template<typename Syscall, class... Args>
|
||||
inline int safe_syscall(Syscall syscall, Args&&... args)
|
||||
{
|
||||
for (;;) {
|
||||
int sysret = syscall(forward<Args>(args)...);
|
||||
if (sysret == -1) {
|
||||
#ifdef SAFE_SYSCALL_DEBUG
|
||||
int saved_errno = errno;
|
||||
dbg() << "Core::safe_syscall: " << sysret << " (" << saved_errno << ": " << strerror(saved_errno) << ")";
|
||||
#endif
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
return sysret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
125
Userland/Libraries/LibCore/TCPServer.cpp
Normal file
125
Userland/Libraries/LibCore/TCPServer.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/TCPServer.h>
|
||||
#include <LibCore/TCPSocket.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
namespace Core {
|
||||
|
||||
TCPServer::TCPServer(Object* parent)
|
||||
: Object(parent)
|
||||
{
|
||||
#ifdef SOCK_NONBLOCK
|
||||
m_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
#else
|
||||
m_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int option = 1;
|
||||
ioctl(m_fd, FIONBIO, &option);
|
||||
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
ASSERT(m_fd >= 0);
|
||||
}
|
||||
|
||||
TCPServer::~TCPServer()
|
||||
{
|
||||
::close(m_fd);
|
||||
}
|
||||
|
||||
bool TCPServer::listen(const IPv4Address& address, u16 port)
|
||||
{
|
||||
if (m_listening)
|
||||
return false;
|
||||
|
||||
auto socket_address = SocketAddress(address, port);
|
||||
auto in = socket_address.to_sockaddr_in();
|
||||
if (::bind(m_fd, (const sockaddr*)&in, sizeof(in)) < 0) {
|
||||
perror("TCPServer::listen: bind");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::listen(m_fd, 5) < 0) {
|
||||
perror("TCPServer::listen: listen");
|
||||
return false;
|
||||
}
|
||||
m_listening = true;
|
||||
|
||||
m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
|
||||
m_notifier->on_ready_to_read = [this] {
|
||||
if (on_ready_to_accept)
|
||||
on_ready_to_accept();
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<TCPSocket> TCPServer::accept()
|
||||
{
|
||||
ASSERT(m_listening);
|
||||
sockaddr_in in;
|
||||
socklen_t in_size = sizeof(in);
|
||||
int accepted_fd = ::accept(m_fd, (sockaddr*)&in, &in_size);
|
||||
if (accepted_fd < 0) {
|
||||
perror("accept");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return TCPSocket::construct(accepted_fd);
|
||||
}
|
||||
|
||||
Optional<IPv4Address> TCPServer::local_address() const
|
||||
{
|
||||
if (m_fd == -1)
|
||||
return {};
|
||||
|
||||
sockaddr_in address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
|
||||
return {};
|
||||
|
||||
return IPv4Address(address.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
Optional<u16> TCPServer::local_port() const
|
||||
{
|
||||
if (m_fd == -1)
|
||||
return {};
|
||||
|
||||
sockaddr_in address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
|
||||
return {};
|
||||
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
|
||||
}
|
58
Userland/Libraries/LibCore/TCPServer.h
Normal file
58
Userland/Libraries/LibCore/TCPServer.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class TCPServer : public Object {
|
||||
C_OBJECT(TCPServer)
|
||||
public:
|
||||
virtual ~TCPServer() override;
|
||||
|
||||
bool is_listening() const { return m_listening; }
|
||||
bool listen(const IPv4Address& address, u16 port);
|
||||
|
||||
RefPtr<TCPSocket> accept();
|
||||
|
||||
Optional<IPv4Address> local_address() const;
|
||||
Optional<u16> local_port() const;
|
||||
|
||||
Function<void()> on_ready_to_accept;
|
||||
|
||||
private:
|
||||
explicit TCPServer(Object* parent = nullptr);
|
||||
|
||||
int m_fd { -1 };
|
||||
bool m_listening { false };
|
||||
RefPtr<Notifier> m_notifier;
|
||||
};
|
||||
|
||||
}
|
70
Userland/Libraries/LibCore/TCPSocket.cpp
Normal file
70
Userland/Libraries/LibCore/TCPSocket.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/TCPSocket.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
TCPSocket::TCPSocket(int fd, Object* parent)
|
||||
: Socket(Socket::Type::TCP, parent)
|
||||
{
|
||||
// NOTE: This constructor is used by TCPServer::accept(), so the socket is already connected.
|
||||
m_connected = true;
|
||||
set_fd(fd);
|
||||
set_mode(IODevice::ReadWrite);
|
||||
set_error(0);
|
||||
}
|
||||
|
||||
TCPSocket::TCPSocket(Object* parent)
|
||||
: Socket(Socket::Type::TCP, parent)
|
||||
{
|
||||
#ifdef SOCK_NONBLOCK
|
||||
int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
#else
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int option = 1;
|
||||
ioctl(fd, FIONBIO, &option);
|
||||
#endif
|
||||
if (fd < 0) {
|
||||
set_error(errno);
|
||||
} else {
|
||||
set_fd(fd);
|
||||
set_mode(IODevice::ReadWrite);
|
||||
set_error(0);
|
||||
}
|
||||
}
|
||||
|
||||
TCPSocket::~TCPSocket()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
44
Userland/Libraries/LibCore/TCPSocket.h
Normal file
44
Userland/Libraries/LibCore/TCPSocket.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <LibCore/Socket.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class TCPSocket final : public Socket {
|
||||
C_OBJECT(TCPSocket)
|
||||
public:
|
||||
virtual ~TCPSocket() override;
|
||||
|
||||
private:
|
||||
TCPSocket(int fd, Object* parent = nullptr);
|
||||
explicit TCPSocket(Object* parent = nullptr);
|
||||
};
|
||||
|
||||
}
|
96
Userland/Libraries/LibCore/Timer.cpp
Normal file
96
Userland/Libraries/LibCore/Timer.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
Timer::Timer(Object* parent)
|
||||
: Object(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Timer::Timer(int interval, Function<void()>&& timeout_handler, Object* parent)
|
||||
: Object(parent)
|
||||
, on_timeout(move(timeout_handler))
|
||||
{
|
||||
start(interval);
|
||||
}
|
||||
|
||||
Timer::~Timer()
|
||||
{
|
||||
}
|
||||
|
||||
void Timer::start()
|
||||
{
|
||||
start(m_interval);
|
||||
}
|
||||
|
||||
void Timer::start(int interval)
|
||||
{
|
||||
if (m_active)
|
||||
return;
|
||||
m_interval = interval;
|
||||
start_timer(interval);
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
void Timer::restart()
|
||||
{
|
||||
restart(m_interval);
|
||||
}
|
||||
|
||||
void Timer::restart(int interval)
|
||||
{
|
||||
if (m_active)
|
||||
stop();
|
||||
start(interval);
|
||||
}
|
||||
|
||||
void Timer::stop()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
stop_timer();
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
void Timer::timer_event(TimerEvent&)
|
||||
{
|
||||
if (m_single_shot)
|
||||
stop();
|
||||
else {
|
||||
if (m_interval_dirty) {
|
||||
stop();
|
||||
start(m_interval);
|
||||
}
|
||||
}
|
||||
|
||||
if (on_timeout)
|
||||
on_timeout();
|
||||
}
|
||||
|
||||
}
|
81
Userland/Libraries/LibCore/Timer.h
Normal file
81
Userland/Libraries/LibCore/Timer.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Timer final : public Object {
|
||||
C_OBJECT(Timer);
|
||||
|
||||
public:
|
||||
static NonnullRefPtr<Timer> create_single_shot(int interval, Function<void()>&& timeout_handler, Object* parent = nullptr)
|
||||
{
|
||||
auto timer = adopt(*new Timer(interval, move(timeout_handler), parent));
|
||||
timer->set_single_shot(true);
|
||||
timer->stop();
|
||||
return timer;
|
||||
}
|
||||
|
||||
virtual ~Timer() override;
|
||||
|
||||
void start();
|
||||
void start(int interval);
|
||||
void restart();
|
||||
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;
|
||||
|
||||
private:
|
||||
explicit Timer(Object* parent = nullptr);
|
||||
Timer(int interval, Function<void()>&& timeout_handler, Object* parent = nullptr);
|
||||
|
||||
virtual void timer_event(TimerEvent&) override;
|
||||
|
||||
bool m_active { false };
|
||||
bool m_single_shot { false };
|
||||
bool m_interval_dirty { false };
|
||||
int m_interval { 0 };
|
||||
};
|
||||
|
||||
}
|
123
Userland/Libraries/LibCore/UDPServer.cpp
Normal file
123
Userland/Libraries/LibCore/UDPServer.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/UDPServer.h>
|
||||
#include <LibCore/UDPSocket.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
UDPServer::UDPServer(Object* parent)
|
||||
: Object(parent)
|
||||
{
|
||||
#ifdef SOCK_NONBLOCK
|
||||
m_fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
#else
|
||||
m_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
int option = 1;
|
||||
ioctl(m_fd, FIONBIO, &option);
|
||||
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
ASSERT(m_fd >= 0);
|
||||
}
|
||||
|
||||
UDPServer::~UDPServer()
|
||||
{
|
||||
::close(m_fd);
|
||||
}
|
||||
|
||||
bool UDPServer::bind(const IPv4Address& address, u16 port)
|
||||
{
|
||||
if (m_bound)
|
||||
return false;
|
||||
|
||||
auto saddr = SocketAddress(address, port);
|
||||
auto in = saddr.to_sockaddr_in();
|
||||
|
||||
if (::bind(m_fd, (const sockaddr*)&in, sizeof(in)) != 0) {
|
||||
perror("UDPServer::bind");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bound = true;
|
||||
|
||||
m_notifier = Notifier::construct(m_fd, Notifier::Event::Read, this);
|
||||
m_notifier->on_ready_to_read = [this] {
|
||||
if (on_ready_to_receive)
|
||||
on_ready_to_receive();
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteBuffer UDPServer::receive(size_t size, sockaddr_in& in)
|
||||
{
|
||||
auto buf = ByteBuffer::create_uninitialized(size);
|
||||
socklen_t in_len = sizeof(in);
|
||||
ssize_t rlen = ::recvfrom(m_fd, buf.data(), size, 0, (sockaddr*)&in, &in_len);
|
||||
if (rlen < 0) {
|
||||
dbgln("recvfrom: {}", strerror(errno));
|
||||
return {};
|
||||
}
|
||||
|
||||
buf.trim(rlen);
|
||||
return buf;
|
||||
}
|
||||
|
||||
Optional<IPv4Address> UDPServer::local_address() const
|
||||
{
|
||||
if (m_fd == -1)
|
||||
return {};
|
||||
|
||||
sockaddr_in address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
|
||||
return {};
|
||||
|
||||
return IPv4Address(address.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
Optional<u16> UDPServer::local_port() const
|
||||
{
|
||||
if (m_fd == -1)
|
||||
return {};
|
||||
|
||||
sockaddr_in address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(m_fd, (sockaddr*)&address, &len) != 0)
|
||||
return {};
|
||||
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
|
||||
}
|
66
Userland/Libraries/LibCore/UDPServer.h
Normal file
66
Userland/Libraries/LibCore/UDPServer.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Function.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibCore/SocketAddress.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class UDPServer : public Object {
|
||||
C_OBJECT(UDPServer)
|
||||
public:
|
||||
virtual ~UDPServer() override;
|
||||
|
||||
bool is_bound() const { return m_bound; }
|
||||
|
||||
bool bind(const IPv4Address& address, u16 port);
|
||||
ByteBuffer receive(size_t size, sockaddr_in& from);
|
||||
ByteBuffer receive(size_t size)
|
||||
{
|
||||
struct sockaddr_in saddr;
|
||||
return receive(size, saddr);
|
||||
};
|
||||
|
||||
Optional<IPv4Address> local_address() const;
|
||||
Optional<u16> local_port() const;
|
||||
|
||||
Function<void()> on_ready_to_receive;
|
||||
|
||||
private:
|
||||
explicit UDPServer(Object* parent = nullptr);
|
||||
|
||||
int m_fd { -1 };
|
||||
bool m_bound { false };
|
||||
RefPtr<Notifier> m_notifier;
|
||||
};
|
||||
|
||||
}
|
61
Userland/Libraries/LibCore/UDPSocket.cpp
Normal file
61
Userland/Libraries/LibCore/UDPSocket.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibCore/UDPSocket.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
UDPSocket::UDPSocket(Object* parent)
|
||||
: Socket(Socket::Type::UDP, parent)
|
||||
{
|
||||
#ifdef SOCK_NONBLOCK
|
||||
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
|
||||
#else
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
int option = 1;
|
||||
ioctl(fd, FIONBIO, &option);
|
||||
#endif
|
||||
|
||||
if (fd < 0) {
|
||||
set_error(errno);
|
||||
} else {
|
||||
set_fd(fd);
|
||||
set_mode(IODevice::ReadWrite);
|
||||
set_error(0);
|
||||
}
|
||||
}
|
||||
|
||||
UDPSocket::~UDPSocket()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
42
Userland/Libraries/LibCore/UDPSocket.h
Normal file
42
Userland/Libraries/LibCore/UDPSocket.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Socket.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class UDPSocket final : public Socket {
|
||||
C_OBJECT(UDPSocket)
|
||||
public:
|
||||
virtual ~UDPSocket() override;
|
||||
|
||||
private:
|
||||
explicit UDPSocket(Object* parent = nullptr);
|
||||
};
|
||||
|
||||
}
|
832
Userland/Libraries/LibCore/puff.cpp
Normal file
832
Userland/Libraries/LibCore/puff.cpp
Normal file
|
@ -0,0 +1,832 @@
|
|||
/*
|
||||
* puff.c
|
||||
* Copyright (C) 2002-2013 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in puff.h
|
||||
* version 2.3, 21 Jan 2013
|
||||
*
|
||||
* puff.c is a simple inflate written to be an unambiguous way to specify the
|
||||
* deflate format. It is not written for speed but rather simplicity. As a
|
||||
* side benefit, this code might actually be useful when small code is more
|
||||
* important than speed, such as bootstrap applications. For typical deflate
|
||||
* data, zlib's inflate() is about four times as fast as puff(). zlib's
|
||||
* inflate compiles to around 20K on my machine, whereas puff.c compiles to
|
||||
* around 4K on my machine (a PowerPC using GNU cc). If the faster decode()
|
||||
* function here is used, then puff() is only twice as slow as zlib's
|
||||
* inflate().
|
||||
*
|
||||
* All dynamically allocated memory comes from the stack. The stack required
|
||||
* is less than 2K bytes. This code is compatible with 16-bit int's and
|
||||
* assumes that long's are at least 32 bits. puff.c uses the short data type,
|
||||
* assumed to be 16 bits, for arrays in order to conserve memory. The code
|
||||
* works whether integers are stored big endian or little endian.
|
||||
*
|
||||
* In the comments below are "Format notes" that describe the inflate process
|
||||
* and document some of the less obvious aspects of the format. This source
|
||||
* code is meant to supplement RFC 1951, which formally describes the deflate
|
||||
* format:
|
||||
*
|
||||
* http://www.zlib.org/rfc-deflate.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* Change history:
|
||||
*
|
||||
* 1.0 10 Feb 2002 - First version
|
||||
* 1.1 17 Feb 2002 - Clarifications of some comments and notes
|
||||
* - Update puff() dest and source pointers on negative
|
||||
* errors to facilitate debugging deflators
|
||||
* - Remove longest from struct huffman -- not needed
|
||||
* - Simplify offs[] index in construct()
|
||||
* - Add input size and checking, using longjmp() to
|
||||
* maintain easy readability
|
||||
* - Use short data type for large arrays
|
||||
* - Use pointers instead of long to specify source and
|
||||
* destination sizes to avoid arbitrary 4 GB limits
|
||||
* 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!),
|
||||
* but leave simple version for readabilty
|
||||
* - Make sure invalid distances detected if pointers
|
||||
* are 16 bits
|
||||
* - Fix fixed codes table error
|
||||
* - Provide a scanning mode for determining size of
|
||||
* uncompressed data
|
||||
* 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly]
|
||||
* - Add a puff.h file for the interface
|
||||
* - Add braces in puff() for else do [Gailly]
|
||||
* - Use indexes instead of pointers for readability
|
||||
* 1.4 31 Mar 2002 - Simplify construct() code set check
|
||||
* - Fix some comments
|
||||
* - Add FIXLCODES #define
|
||||
* 1.5 6 Apr 2002 - Minor comment fixes
|
||||
* 1.6 7 Aug 2002 - Minor format changes
|
||||
* 1.7 3 Mar 2003 - Added test code for distribution
|
||||
* - Added zlib-like license
|
||||
* 1.8 9 Jan 2004 - Added some comments on no distance codes case
|
||||
* 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland]
|
||||
* - Catch missing end-of-block symbol error
|
||||
* 2.0 25 Jul 2008 - Add #define to permit distance too far back
|
||||
* - Add option in TEST code for puff to write the data
|
||||
* - Add option in TEST code to skip input bytes
|
||||
* - Allow TEST code to read from piped stdin
|
||||
* 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers
|
||||
* - Avoid unsigned comparisons for even happier compilers
|
||||
* 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer]
|
||||
* - Add const where appropriate [Oberhumer]
|
||||
* - Split if's and ?'s for coverage testing
|
||||
* - Break out test code to separate file
|
||||
* - Move NIL to puff.h
|
||||
* - Allow incomplete code only if single code length is 1
|
||||
* - Add full code coverage test to Makefile
|
||||
* 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks
|
||||
*/
|
||||
|
||||
#include "puff.h" /* prototype for puff() */
|
||||
#include <setjmp.h> /* for setjmp(), longjmp(), and jmp_buf */
|
||||
|
||||
#define local static /* for local function definitions */
|
||||
|
||||
/*
|
||||
* Maximums for allocations and loops. It is not useful to change these --
|
||||
* they are fixed by the deflate format.
|
||||
*/
|
||||
#define MAXBITS 15 /* maximum bits in a code */
|
||||
#define MAXLCODES 286 /* maximum number of literal/length codes */
|
||||
#define MAXDCODES 30 /* maximum number of distance codes */
|
||||
#define MAXCODES (MAXLCODES + MAXDCODES) /* maximum codes lengths to read */
|
||||
#define FIXLCODES 288 /* number of fixed literal/length codes */
|
||||
|
||||
/* input and output state */
|
||||
struct state {
|
||||
/* output state */
|
||||
unsigned char* out; /* output buffer */
|
||||
unsigned long outlen; /* available space at out */
|
||||
unsigned long outcnt; /* bytes written to out so far */
|
||||
|
||||
/* input state */
|
||||
const unsigned char* in; /* input buffer */
|
||||
unsigned long inlen; /* available input at in */
|
||||
unsigned long incnt; /* bytes read so far */
|
||||
int bitbuf; /* bit buffer */
|
||||
int bitcnt; /* number of bits in bit buffer */
|
||||
|
||||
/* input limit error return state for bits() and decode() */
|
||||
jmp_buf env;
|
||||
};
|
||||
|
||||
/*
|
||||
* Return need bits from the input stream. This always leaves less than
|
||||
* eight bits in the buffer. bits() works properly for need == 0.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - Bits are stored in bytes from the least significant bit to the most
|
||||
* significant bit. Therefore bits are dropped from the bottom of the bit
|
||||
* buffer, using shift right, and new bytes are appended to the top of the
|
||||
* bit buffer, using shift left.
|
||||
*/
|
||||
local int bits(struct state* s, int need)
|
||||
{
|
||||
long val; /* bit accumulator (can use up to 20 bits) */
|
||||
|
||||
/* load at least need bits into val */
|
||||
val = s->bitbuf;
|
||||
while (s->bitcnt < need) {
|
||||
if (s->incnt == s->inlen)
|
||||
longjmp(s->env, 1); /* out of input */
|
||||
val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */
|
||||
s->bitcnt += 8;
|
||||
}
|
||||
|
||||
/* drop need bits and update buffer, always zero to seven bits left */
|
||||
s->bitbuf = (int)(val >> need);
|
||||
s->bitcnt -= need;
|
||||
|
||||
/* return need bits, zeroing the bits above that */
|
||||
return (int)(val & ((1L << need) - 1));
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a stored block.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - After the two-bit stored block type (00), the stored block length and
|
||||
* stored bytes are byte-aligned for fast copying. Therefore any leftover
|
||||
* bits in the byte that has the last bit of the type, as many as seven, are
|
||||
* discarded. The value of the discarded bits are not defined and should not
|
||||
* be checked against any expectation.
|
||||
*
|
||||
* - The second inverted copy of the stored block length does not have to be
|
||||
* checked, but it's probably a good idea to do so anyway.
|
||||
*
|
||||
* - A stored block can have zero length. This is sometimes used to byte-align
|
||||
* subsets of the compressed data for random access or partial recovery.
|
||||
*/
|
||||
local int stored(struct state* s)
|
||||
{
|
||||
unsigned len; /* length of stored block */
|
||||
|
||||
/* discard leftover bits from current byte (assumes s->bitcnt < 8) */
|
||||
s->bitbuf = 0;
|
||||
s->bitcnt = 0;
|
||||
|
||||
/* get length and check against its one's complement */
|
||||
if (s->incnt + 4 > s->inlen)
|
||||
return 2; /* not enough input */
|
||||
len = s->in[s->incnt++];
|
||||
len |= s->in[s->incnt++] << 8;
|
||||
if (s->in[s->incnt++] != (~len & 0xff) || s->in[s->incnt++] != ((~len >> 8) & 0xff))
|
||||
return -2; /* didn't match complement! */
|
||||
|
||||
/* copy len bytes from in to out */
|
||||
if (s->incnt + len > s->inlen)
|
||||
return 2; /* not enough input */
|
||||
if (s->out != NIL) {
|
||||
if (s->outcnt + len > s->outlen)
|
||||
return 1; /* not enough output space */
|
||||
while (len--)
|
||||
s->out[s->outcnt++] = s->in[s->incnt++];
|
||||
} else { /* just scanning */
|
||||
s->outcnt += len;
|
||||
s->incnt += len;
|
||||
}
|
||||
|
||||
/* done with a valid stored block */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
|
||||
* each length, which for a canonical code are stepped through in order.
|
||||
* symbol[] are the symbol values in canonical order, where the number of
|
||||
* entries is the sum of the counts in count[]. The decoding process can be
|
||||
* seen in the function decode() below.
|
||||
*/
|
||||
struct huffman {
|
||||
short* count; /* number of symbols of each length */
|
||||
short* symbol; /* canonically ordered symbols */
|
||||
};
|
||||
|
||||
/*
|
||||
* Decode a code from the stream s using huffman table h. Return the symbol or
|
||||
* a negative value if there is an error. If all of the lengths are zero, i.e.
|
||||
* an empty code, or if the code is incomplete and an invalid code is received,
|
||||
* then -10 is returned after reading MAXBITS bits.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - The codes as stored in the compressed data are bit-reversed relative to
|
||||
* a simple integer ordering of codes of the same lengths. Hence below the
|
||||
* bits are pulled from the compressed data one at a time and used to
|
||||
* build the code value reversed from what is in the stream in order to
|
||||
* permit simple integer comparisons for decoding. A table-based decoding
|
||||
* scheme (as used in zlib) does not need to do this reversal.
|
||||
*
|
||||
* - The first code for the shortest length is all zeros. Subsequent codes of
|
||||
* the same length are simply integer increments of the previous code. When
|
||||
* moving up a length, a zero bit is appended to the code. For a complete
|
||||
* code, the last code of the longest length will be all ones.
|
||||
*
|
||||
* - Incomplete codes are handled by this decoder, since they are permitted
|
||||
* in the deflate format. See the format notes for fixed() and dynamic().
|
||||
*/
|
||||
#ifdef SLOW
|
||||
local int decode(struct state* s, const struct huffman* h)
|
||||
{
|
||||
int len; /* current number of bits in code */
|
||||
int code; /* len bits being decoded */
|
||||
int first; /* first code of length len */
|
||||
int count; /* number of codes of length len */
|
||||
int index; /* index of first code of length len in symbol table */
|
||||
|
||||
code = first = index = 0;
|
||||
for (len = 1; len <= MAXBITS; len++) {
|
||||
code |= bits(s, 1); /* get next bit */
|
||||
count = h->count[len];
|
||||
if (code - count < first) /* if length len, return symbol */
|
||||
return h->symbol[index + (code - first)];
|
||||
index += count; /* else update for next length */
|
||||
first += count;
|
||||
first <<= 1;
|
||||
code <<= 1;
|
||||
}
|
||||
return -10; /* ran out of codes */
|
||||
}
|
||||
|
||||
/*
|
||||
* A faster version of decode() for real applications of this code. It's not
|
||||
* as readable, but it makes puff() twice as fast. And it only makes the code
|
||||
* a few percent larger.
|
||||
*/
|
||||
#else /* !SLOW */
|
||||
local int decode(struct state* s, const struct huffman* h)
|
||||
{
|
||||
int len; /* current number of bits in code */
|
||||
int code; /* len bits being decoded */
|
||||
int first; /* first code of length len */
|
||||
int count; /* number of codes of length len */
|
||||
int index; /* index of first code of length len in symbol table */
|
||||
int bitbuf; /* bits from stream */
|
||||
int left; /* bits left in next or left to process */
|
||||
short* next; /* next number of codes */
|
||||
|
||||
bitbuf = s->bitbuf;
|
||||
left = s->bitcnt;
|
||||
code = first = index = 0;
|
||||
len = 1;
|
||||
next = h->count + 1;
|
||||
while (1) {
|
||||
while (left--) {
|
||||
code |= bitbuf & 1;
|
||||
bitbuf >>= 1;
|
||||
count = *next++;
|
||||
if (code - count < first) { /* if length len, return symbol */
|
||||
s->bitbuf = bitbuf;
|
||||
s->bitcnt = (s->bitcnt - len) & 7;
|
||||
return h->symbol[index + (code - first)];
|
||||
}
|
||||
index += count; /* else update for next length */
|
||||
first += count;
|
||||
first <<= 1;
|
||||
code <<= 1;
|
||||
len++;
|
||||
}
|
||||
left = (MAXBITS + 1) - len;
|
||||
if (left == 0)
|
||||
break;
|
||||
if (s->incnt == s->inlen)
|
||||
longjmp(s->env, 1); /* out of input */
|
||||
bitbuf = s->in[s->incnt++];
|
||||
if (left > 8)
|
||||
left = 8;
|
||||
}
|
||||
return -10; /* ran out of codes */
|
||||
}
|
||||
#endif /* SLOW */
|
||||
|
||||
/*
|
||||
* Given the list of code lengths length[0..n-1] representing a canonical
|
||||
* Huffman code for n symbols, construct the tables required to decode those
|
||||
* codes. Those tables are the number of codes of each length, and the symbols
|
||||
* sorted by length, retaining their original order within each length. The
|
||||
* return value is zero for a complete code set, negative for an over-
|
||||
* subscribed code set, and positive for an incomplete code set. The tables
|
||||
* can be used if the return value is zero or positive, but they cannot be used
|
||||
* if the return value is negative. If the return value is zero, it is not
|
||||
* possible for decode() using that table to return an error--any stream of
|
||||
* enough bits will resolve to a symbol. If the return value is positive, then
|
||||
* it is possible for decode() using that table to return an error for received
|
||||
* codes past the end of the incomplete lengths.
|
||||
*
|
||||
* Not used by decode(), but used for error checking, h->count[0] is the number
|
||||
* of the n symbols not in the code. So n - h->count[0] is the number of
|
||||
* codes. This is useful for checking for incomplete codes that have more than
|
||||
* one symbol, which is an error in a dynamic block.
|
||||
*
|
||||
* Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS
|
||||
* This is assured by the construction of the length arrays in dynamic() and
|
||||
* fixed() and is not verified by construct().
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - Permitted and expected examples of incomplete codes are one of the fixed
|
||||
* codes and any code with a single symbol which in deflate is coded as one
|
||||
* bit instead of zero bits. See the format notes for fixed() and dynamic().
|
||||
*
|
||||
* - Within a given code length, the symbols are kept in ascending order for
|
||||
* the code bits definition.
|
||||
*/
|
||||
local int construct(struct huffman* h, const short* length, int n)
|
||||
{
|
||||
int symbol; /* current symbol when stepping through length[] */
|
||||
int len; /* current length when stepping through h->count[] */
|
||||
int left; /* number of possible codes left of current length */
|
||||
short offs[MAXBITS + 1]; /* offsets in symbol table for each length */
|
||||
|
||||
/* count number of codes of each length */
|
||||
for (len = 0; len <= MAXBITS; len++)
|
||||
h->count[len] = 0;
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
(h->count[length[symbol]])++; /* assumes lengths are within bounds */
|
||||
if (h->count[0] == n) /* no codes! */
|
||||
return 0; /* complete, but decode() will fail */
|
||||
|
||||
/* check for an over-subscribed or incomplete set of lengths */
|
||||
left = 1; /* one possible code of zero length */
|
||||
for (len = 1; len <= MAXBITS; len++) {
|
||||
left <<= 1; /* one more bit, double codes left */
|
||||
left -= h->count[len]; /* deduct count from possible codes */
|
||||
if (left < 0)
|
||||
return left; /* over-subscribed--return negative */
|
||||
} /* left > 0 means incomplete */
|
||||
|
||||
/* generate offsets into symbol table for each length for sorting */
|
||||
offs[1] = 0;
|
||||
for (len = 1; len < MAXBITS; len++)
|
||||
offs[len + 1] = offs[len] + h->count[len];
|
||||
|
||||
/*
|
||||
* put symbols in table sorted by length, by symbol order within each
|
||||
* length
|
||||
*/
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
if (length[symbol] != 0)
|
||||
h->symbol[offs[length[symbol]]++] = symbol;
|
||||
|
||||
/* return zero for complete set, positive for incomplete set */
|
||||
return left;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode literal/length and distance codes until an end-of-block code.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - Compressed data that is after the block type if fixed or after the code
|
||||
* description if dynamic is a combination of literals and length/distance
|
||||
* pairs terminated by and end-of-block code. Literals are simply Huffman
|
||||
* coded bytes. A length/distance pair is a coded length followed by a
|
||||
* coded distance to represent a string that occurs earlier in the
|
||||
* uncompressed data that occurs again at the current location.
|
||||
*
|
||||
* - Literals, lengths, and the end-of-block code are combined into a single
|
||||
* code of up to 286 symbols. They are 256 literals (0..255), 29 length
|
||||
* symbols (257..285), and the end-of-block symbol (256).
|
||||
*
|
||||
* - There are 256 possible lengths (3..258), and so 29 symbols are not enough
|
||||
* to represent all of those. Lengths 3..10 and 258 are in fact represented
|
||||
* by just a length symbol. Lengths 11..257 are represented as a symbol and
|
||||
* some number of extra bits that are added as an integer to the base length
|
||||
* of the length symbol. The number of extra bits is determined by the base
|
||||
* length symbol. These are in the static arrays below, lens[] for the base
|
||||
* lengths and lext[] for the corresponding number of extra bits.
|
||||
*
|
||||
* - The reason that 258 gets its own symbol is that the longest length is used
|
||||
* often in highly redundant files. Note that 258 can also be coded as the
|
||||
* base value 227 plus the maximum extra value of 31. While a good deflate
|
||||
* should never do this, it is not an error, and should be decoded properly.
|
||||
*
|
||||
* - If a length is decoded, including its extra bits if any, then it is
|
||||
* followed a distance code. There are up to 30 distance symbols. Again
|
||||
* there are many more possible distances (1..32768), so extra bits are added
|
||||
* to a base value represented by the symbol. The distances 1..4 get their
|
||||
* own symbol, but the rest require extra bits. The base distances and
|
||||
* corresponding number of extra bits are below in the static arrays dist[]
|
||||
* and dext[].
|
||||
*
|
||||
* - Literal bytes are simply written to the output. A length/distance pair is
|
||||
* an instruction to copy previously uncompressed bytes to the output. The
|
||||
* copy is from distance bytes back in the output stream, copying for length
|
||||
* bytes.
|
||||
*
|
||||
* - Distances pointing before the beginning of the output data are not
|
||||
* permitted.
|
||||
*
|
||||
* - Overlapped copies, where the length is greater than the distance, are
|
||||
* allowed and common. For example, a distance of one and a length of 258
|
||||
* simply copies the last byte 258 times. A distance of four and a length of
|
||||
* twelve copies the last four bytes three times. A simple forward copy
|
||||
* ignoring whether the length is greater than the distance or not implements
|
||||
* this correctly. You should not use memcpy() since its behavior is not
|
||||
* defined for overlapped arrays. You should not use memmove() or bcopy()
|
||||
* since though their behavior -is- defined for overlapping arrays, it is
|
||||
* defined to do the wrong thing in this case.
|
||||
*/
|
||||
local int codes(struct state* s,
|
||||
const struct huffman* lencode,
|
||||
const struct huffman* distcode)
|
||||
{
|
||||
int symbol; /* decoded symbol */
|
||||
int len; /* length for copy */
|
||||
unsigned dist; /* distance for copy */
|
||||
static const short lens[29] = { /* Size base for length codes 257..285 */
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
|
||||
};
|
||||
static const short lext[29] = { /* Extra bits for length codes 257..285 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
|
||||
};
|
||||
static const short dists[30] = { /* Offset base for distance codes 0..29 */
|
||||
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||
8193, 12289, 16385, 24577
|
||||
};
|
||||
static const short dext[30] = { /* Extra bits for distance codes 0..29 */
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
||||
12, 12, 13, 13
|
||||
};
|
||||
|
||||
/* decode literals and length/distance pairs */
|
||||
do {
|
||||
symbol = decode(s, lencode);
|
||||
if (symbol < 0)
|
||||
return symbol; /* invalid symbol */
|
||||
if (symbol < 256) { /* literal: symbol is the byte */
|
||||
/* write out the literal */
|
||||
if (s->out != NIL) {
|
||||
if (s->outcnt == s->outlen)
|
||||
return 1;
|
||||
s->out[s->outcnt] = symbol;
|
||||
}
|
||||
s->outcnt++;
|
||||
} else if (symbol > 256) { /* length */
|
||||
/* get and compute length */
|
||||
symbol -= 257;
|
||||
if (symbol >= 29)
|
||||
return -10; /* invalid fixed code */
|
||||
len = lens[symbol] + bits(s, lext[symbol]);
|
||||
|
||||
/* get and check distance */
|
||||
symbol = decode(s, distcode);
|
||||
if (symbol < 0)
|
||||
return symbol; /* invalid symbol */
|
||||
dist = dists[symbol] + bits(s, dext[symbol]);
|
||||
#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
|
||||
if (dist > s->outcnt)
|
||||
return -11; /* distance too far back */
|
||||
#endif
|
||||
|
||||
/* copy length bytes from distance bytes back */
|
||||
if (s->out != NIL) {
|
||||
if (s->outcnt + len > s->outlen)
|
||||
return 1;
|
||||
while (len--) {
|
||||
s->out[s->outcnt] =
|
||||
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
|
||||
dist > s->outcnt ? 0 :
|
||||
#endif
|
||||
s->out[s->outcnt - dist];
|
||||
s->outcnt++;
|
||||
}
|
||||
} else
|
||||
s->outcnt += len;
|
||||
}
|
||||
} while (symbol != 256); /* end of block symbol */
|
||||
|
||||
/* done with a valid fixed or dynamic block */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a fixed codes block.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - This block type can be useful for compressing small amounts of data for
|
||||
* which the size of the code descriptions in a dynamic block exceeds the
|
||||
* benefit of custom codes for that block. For fixed codes, no bits are
|
||||
* spent on code descriptions. Instead the code lengths for literal/length
|
||||
* codes and distance codes are fixed. The specific lengths for each symbol
|
||||
* can be seen in the "for" loops below.
|
||||
*
|
||||
* - The literal/length code is complete, but has two symbols that are invalid
|
||||
* and should result in an error if received. This cannot be implemented
|
||||
* simply as an incomplete code since those two symbols are in the "middle"
|
||||
* of the code. They are eight bits long and the longest literal/length\
|
||||
* code is nine bits. Therefore the code must be constructed with those
|
||||
* symbols, and the invalid symbols must be detected after decoding.
|
||||
*
|
||||
* - The fixed distance codes also have two invalid symbols that should result
|
||||
* in an error if received. Since all of the distance codes are the same
|
||||
* length, this can be implemented as an incomplete code. Then the invalid
|
||||
* codes are detected while decoding.
|
||||
*/
|
||||
local int fixed(struct state* s)
|
||||
{
|
||||
static int virgin = 1;
|
||||
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
|
||||
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
|
||||
static struct huffman lencode, distcode;
|
||||
|
||||
/* build fixed huffman tables if first call (may not be thread safe) */
|
||||
if (virgin) {
|
||||
int symbol;
|
||||
short lengths[FIXLCODES];
|
||||
|
||||
/* construct lencode and distcode */
|
||||
lencode.count = lencnt;
|
||||
lencode.symbol = lensym;
|
||||
distcode.count = distcnt;
|
||||
distcode.symbol = distsym;
|
||||
|
||||
/* literal/length table */
|
||||
for (symbol = 0; symbol < 144; symbol++)
|
||||
lengths[symbol] = 8;
|
||||
for (; symbol < 256; symbol++)
|
||||
lengths[symbol] = 9;
|
||||
for (; symbol < 280; symbol++)
|
||||
lengths[symbol] = 7;
|
||||
for (; symbol < FIXLCODES; symbol++)
|
||||
lengths[symbol] = 8;
|
||||
construct(&lencode, lengths, FIXLCODES);
|
||||
|
||||
/* distance table */
|
||||
for (symbol = 0; symbol < MAXDCODES; symbol++)
|
||||
lengths[symbol] = 5;
|
||||
construct(&distcode, lengths, MAXDCODES);
|
||||
|
||||
/* do this just once */
|
||||
virgin = 0;
|
||||
}
|
||||
|
||||
/* decode data until end-of-block code */
|
||||
return codes(s, &lencode, &distcode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a dynamic codes block.
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - A dynamic block starts with a description of the literal/length and
|
||||
* distance codes for that block. New dynamic blocks allow the compressor to
|
||||
* rapidly adapt to changing data with new codes optimized for that data.
|
||||
*
|
||||
* - The codes used by the deflate format are "canonical", which means that
|
||||
* the actual bits of the codes are generated in an unambiguous way simply
|
||||
* from the number of bits in each code. Therefore the code descriptions
|
||||
* are simply a list of code lengths for each symbol.
|
||||
*
|
||||
* - The code lengths are stored in order for the symbols, so lengths are
|
||||
* provided for each of the literal/length symbols, and for each of the
|
||||
* distance symbols.
|
||||
*
|
||||
* - If a symbol is not used in the block, this is represented by a zero as
|
||||
* as the code length. This does not mean a zero-length code, but rather
|
||||
* that no code should be created for this symbol. There is no way in the
|
||||
* deflate format to represent a zero-length code.
|
||||
*
|
||||
* - The maximum number of bits in a code is 15, so the possible lengths for
|
||||
* any code are 1..15.
|
||||
*
|
||||
* - The fact that a length of zero is not permitted for a code has an
|
||||
* interesting consequence. Normally if only one symbol is used for a given
|
||||
* code, then in fact that code could be represented with zero bits. However
|
||||
* in deflate, that code has to be at least one bit. So for example, if
|
||||
* only a single distance base symbol appears in a block, then it will be
|
||||
* represented by a single code of length one, in particular one 0 bit. This
|
||||
* is an incomplete code, since if a 1 bit is received, it has no meaning,
|
||||
* and should result in an error. So incomplete distance codes of one symbol
|
||||
* should be permitted, and the receipt of invalid codes should be handled.
|
||||
*
|
||||
* - It is also possible to have a single literal/length code, but that code
|
||||
* must be the end-of-block code, since every dynamic block has one. This
|
||||
* is not the most efficient way to create an empty block (an empty fixed
|
||||
* block is fewer bits), but it is allowed by the format. So incomplete
|
||||
* literal/length codes of one symbol should also be permitted.
|
||||
*
|
||||
* - If there are only literal codes and no lengths, then there are no distance
|
||||
* codes. This is represented by one distance code with zero bits.
|
||||
*
|
||||
* - The list of up to 286 length/literal lengths and up to 30 distance lengths
|
||||
* are themselves compressed using Huffman codes and run-length encoding. In
|
||||
* the list of code lengths, a 0 symbol means no code, a 1..15 symbol means
|
||||
* that length, and the symbols 16, 17, and 18 are run-length instructions.
|
||||
* Each of 16, 17, and 18 are follwed by extra bits to define the length of
|
||||
* the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10
|
||||
* zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols
|
||||
* are common, hence the special coding for zero lengths.
|
||||
*
|
||||
* - The symbols for 0..18 are Huffman coded, and so that code must be
|
||||
* described first. This is simply a sequence of up to 19 three-bit values
|
||||
* representing no code (0) or the code length for that symbol (1..7).
|
||||
*
|
||||
* - A dynamic block starts with three fixed-size counts from which is computed
|
||||
* the number of literal/length code lengths, the number of distance code
|
||||
* lengths, and the number of code length code lengths (ok, you come up with
|
||||
* a better name!) in the code descriptions. For the literal/length and
|
||||
* distance codes, lengths after those provided are considered zero, i.e. no
|
||||
* code. The code length code lengths are received in a permuted order (see
|
||||
* the order[] array below) to make a short code length code length list more
|
||||
* likely. As it turns out, very short and very long codes are less likely
|
||||
* to be seen in a dynamic code description, hence what may appear initially
|
||||
* to be a peculiar ordering.
|
||||
*
|
||||
* - Given the number of literal/length code lengths (nlen) and distance code
|
||||
* lengths (ndist), then they are treated as one long list of nlen + ndist
|
||||
* code lengths. Therefore run-length coding can and often does cross the
|
||||
* boundary between the two sets of lengths.
|
||||
*
|
||||
* - So to summarize, the code description at the start of a dynamic block is
|
||||
* three counts for the number of code lengths for the literal/length codes,
|
||||
* the distance codes, and the code length codes. This is followed by the
|
||||
* code length code lengths, three bits each. This is used to construct the
|
||||
* code length code which is used to read the remainder of the lengths. Then
|
||||
* the literal/length code lengths and distance lengths are read as a single
|
||||
* set of lengths using the code length codes. Codes are constructed from
|
||||
* the resulting two sets of lengths, and then finally you can start
|
||||
* decoding actual compressed data in the block.
|
||||
*
|
||||
* - For reference, a "typical" size for the code description in a dynamic
|
||||
* block is around 80 bytes.
|
||||
*/
|
||||
local int dynamic(struct state* s)
|
||||
{
|
||||
int nlen, ndist, ncode; /* number of lengths in descriptor */
|
||||
int index; /* index of lengths[] */
|
||||
int err; /* construct() return value */
|
||||
short lengths[MAXCODES]; /* descriptor code lengths */
|
||||
short lencnt[MAXBITS + 1], lensym[MAXLCODES]; /* lencode memory */
|
||||
short distcnt[MAXBITS + 1], distsym[MAXDCODES]; /* distcode memory */
|
||||
struct huffman lencode, distcode; /* length and distance codes */
|
||||
static const short order[19] = /* permutation of code length codes */
|
||||
{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
|
||||
|
||||
/* construct lencode and distcode */
|
||||
lencode.count = lencnt;
|
||||
lencode.symbol = lensym;
|
||||
distcode.count = distcnt;
|
||||
distcode.symbol = distsym;
|
||||
|
||||
/* get number of lengths in each table, check lengths */
|
||||
nlen = bits(s, 5) + 257;
|
||||
ndist = bits(s, 5) + 1;
|
||||
ncode = bits(s, 4) + 4;
|
||||
if (nlen > MAXLCODES || ndist > MAXDCODES)
|
||||
return -3; /* bad counts */
|
||||
|
||||
/* read code length code lengths (really), missing lengths are zero */
|
||||
for (index = 0; index < ncode; index++)
|
||||
lengths[order[index]] = bits(s, 3);
|
||||
for (; index < 19; index++)
|
||||
lengths[order[index]] = 0;
|
||||
|
||||
/* build huffman table for code lengths codes (use lencode temporarily) */
|
||||
err = construct(&lencode, lengths, 19);
|
||||
if (err != 0) /* require complete code set here */
|
||||
return -4;
|
||||
|
||||
/* read length/literal and distance code length tables */
|
||||
index = 0;
|
||||
while (index < nlen + ndist) {
|
||||
int symbol; /* decoded value */
|
||||
int len; /* last length to repeat */
|
||||
|
||||
symbol = decode(s, &lencode);
|
||||
if (symbol < 0)
|
||||
return symbol; /* invalid symbol */
|
||||
if (symbol < 16) /* length in 0..15 */
|
||||
lengths[index++] = symbol;
|
||||
else { /* repeat instruction */
|
||||
len = 0; /* assume repeating zeros */
|
||||
if (symbol == 16) { /* repeat last length 3..6 times */
|
||||
if (index == 0)
|
||||
return -5; /* no last length! */
|
||||
len = lengths[index - 1]; /* last length */
|
||||
symbol = 3 + bits(s, 2);
|
||||
} else if (symbol == 17) /* repeat zero 3..10 times */
|
||||
symbol = 3 + bits(s, 3);
|
||||
else /* == 18, repeat zero 11..138 times */
|
||||
symbol = 11 + bits(s, 7);
|
||||
if (index + symbol > nlen + ndist)
|
||||
return -6; /* too many lengths! */
|
||||
while (symbol--) /* repeat last or zero symbol times */
|
||||
lengths[index++] = len;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for end-of-block code -- there better be one! */
|
||||
if (lengths[256] == 0)
|
||||
return -9;
|
||||
|
||||
/* build huffman table for literal/length codes */
|
||||
err = construct(&lencode, lengths, nlen);
|
||||
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
|
||||
return -7; /* incomplete code ok only for single length 1 code */
|
||||
|
||||
/* build huffman table for distance codes */
|
||||
err = construct(&distcode, lengths + nlen, ndist);
|
||||
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
|
||||
return -8; /* incomplete code ok only for single length 1 code */
|
||||
|
||||
/* decode data until end-of-block code */
|
||||
return codes(s, &lencode, &distcode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Inflate source to dest. On return, destlen and sourcelen are updated to the
|
||||
* size of the uncompressed data and the size of the deflate data respectively.
|
||||
* On success, the return value of puff() is zero. If there is an error in the
|
||||
* source data, i.e. it is not in the deflate format, then a negative value is
|
||||
* returned. If there is not enough input available or there is not enough
|
||||
* output space, then a positive error is returned. In that case, destlen and
|
||||
* sourcelen are not updated to facilitate retrying from the beginning with the
|
||||
* provision of more input data or more output space. In the case of invalid
|
||||
* inflate data (a negative error), the dest and source pointers are updated to
|
||||
* facilitate the debugging of deflators.
|
||||
*
|
||||
* puff() also has a mode to determine the size of the uncompressed output with
|
||||
* no output written. For this dest must be (unsigned char *)0. In this case,
|
||||
* the input value of *destlen is ignored, and on return *destlen is set to the
|
||||
* size of the uncompressed output.
|
||||
*
|
||||
* The return codes are:
|
||||
*
|
||||
* 2: available inflate data did not terminate
|
||||
* 1: output space exhausted before completing inflate
|
||||
* 0: successful inflate
|
||||
* -1: invalid block type (type == 3)
|
||||
* -2: stored block length did not match one's complement
|
||||
* -3: dynamic block code description: too many length or distance codes
|
||||
* -4: dynamic block code description: code lengths codes incomplete
|
||||
* -5: dynamic block code description: repeat lengths with no first length
|
||||
* -6: dynamic block code description: repeat more than specified lengths
|
||||
* -7: dynamic block code description: invalid literal/length code lengths
|
||||
* -8: dynamic block code description: invalid distance code lengths
|
||||
* -9: dynamic block code description: missing end-of-block code
|
||||
* -10: invalid literal/length or distance code in fixed or dynamic block
|
||||
* -11: distance is too far back in fixed or dynamic block
|
||||
*
|
||||
* Format notes:
|
||||
*
|
||||
* - Three bits are read for each block to determine the kind of block and
|
||||
* whether or not it is the last block. Then the block is decoded and the
|
||||
* process repeated if it was not the last block.
|
||||
*
|
||||
* - The leftover bits in the last byte of the deflate data after the last
|
||||
* block (if it was a fixed or dynamic block) are undefined and have no
|
||||
* expected values to check.
|
||||
*/
|
||||
int puff(unsigned char* dest, /* pointer to destination pointer */
|
||||
unsigned long* destlen, /* amount of output space */
|
||||
const unsigned char* source, /* pointer to source data pointer */
|
||||
unsigned long* sourcelen) /* amount of input available */
|
||||
{
|
||||
struct state s; /* input/output state */
|
||||
int last, type; /* block information */
|
||||
int err; /* return value */
|
||||
|
||||
/* initialize output state */
|
||||
s.out = dest;
|
||||
s.outlen = *destlen; /* ignored if dest is NIL */
|
||||
s.outcnt = 0;
|
||||
|
||||
/* initialize input state */
|
||||
s.in = source;
|
||||
s.inlen = *sourcelen;
|
||||
s.incnt = 0;
|
||||
s.bitbuf = 0;
|
||||
s.bitcnt = 0;
|
||||
|
||||
/* return if bits() or decode() tries to read past available input */
|
||||
if (setjmp(s.env) != 0) /* if came back here via longjmp() */
|
||||
err = 2; /* then skip do-loop, return error */
|
||||
else {
|
||||
/* process blocks until last block or error */
|
||||
do {
|
||||
last = bits(&s, 1); /* one if last block */
|
||||
type = bits(&s, 2); /* block type 0..3 */
|
||||
err = type == 0 ? stored(&s) : (type == 1 ? fixed(&s) : (type == 2 ? dynamic(&s) : -1)); /* type == 3, invalid */
|
||||
if (err != 0)
|
||||
break; /* return with error */
|
||||
} while (!last);
|
||||
}
|
||||
|
||||
/* update the lengths and return */
|
||||
if (err <= 0) {
|
||||
*destlen = s.outcnt;
|
||||
*sourcelen = s.incnt;
|
||||
}
|
||||
return err;
|
||||
}
|
44
Userland/Libraries/LibCore/puff.h
Normal file
44
Userland/Libraries/LibCore/puff.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
/* puff.h
|
||||
Copyright (C) 2002-2013 Mark Adler, all rights reserved
|
||||
version 2.3, 21 Jan 2013
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Mark Adler madler@alumni.caltech.edu
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* See puff.c for purpose and usage.
|
||||
*/
|
||||
#ifndef NIL
|
||||
# define NIL ((unsigned char*)0) /* for no output option */
|
||||
#endif
|
||||
|
||||
int puff(unsigned char* dest, /* pointer to destination pointer */
|
||||
unsigned long* destlen, /* amount of output space */
|
||||
const unsigned char* source, /* pointer to source data pointer */
|
||||
unsigned long* sourcelen); /* amount of input available */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue