Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.4.1

- **A standard stream kept (`stdio::capture`).** `capture::begin(stream::error)`
(or `stream::output`) makes the stream a file until `text()` or destruction puts
it back, and `text()` gives what was written meanwhile: through openkal, through
the C library and the C++ streams, and by the programs started meanwhile. A
test reads what a program prints with it. Each system rearranges its own
descriptor (the kernel's on Linux and macOS, the C runtime's and the standard
handle on Windows, the C library's under Emscripten); an image answers
`not_supported`, as it does for `output_to_error`.
- **What a program used (`process::resources`).** `resources(of::self)` gives
this program's user and system processor time, its largest resident set and
the bytes its reads and writes moved; `resources(of::children)` the same of
the programs it started, waited for and whose handles it closed (which reaps
them), together, as the kernel accounts them
(Linux and macOS, by `getrusage`). A field the system does not tell is absent;
Windows tells a program's own use and not its children's; an image and the Web
answer `not_supported`. A test runner measures a program by starting it from a
process of its own and asking for that process's children.

## 0.4.0

- **`is_openkal`, named as its platform is.** A build for the openkal target
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "os"
version = "0.4.0"
version = "0.4.1"
description = "mcpplibs.os: a C++ operating-system library over openkal --- processes, files, environment, terminals, directories and local IPC, with compile-time platform constants and a few platform modules."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand Down
4 changes: 4 additions & 0 deletions src/emscripten/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ result<void> replace_image(const std::filesystem::path&, const std::vector<std::
return std::unexpected(error{ errc::not_supported, "this system replaces no program's image" });
}

result<usage> resources(of) {
return std::unexpected(error{ errc::not_supported, "this system tells no program's use of its resources" });
}

} // namespace mcpplibs::os::process::sys
18 changes: 18 additions & 0 deletions src/emscripten/stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ void restore_output(int saved) {
::close(saved);
}

int divert_to_file(int descriptor, const std::filesystem::path& file) {
const int target = ::open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
if (target < 0) return -1;
const int saved = ::fcntl(descriptor, F_DUPFD_CLOEXEC, 3);
if (saved < 0 || ::dup2(target, descriptor) < 0) {
if (saved >= 0) ::close(saved);
::close(target);
return -1;
}
::close(target);
return saved;
}

void restore_descriptor(int descriptor, int saved) {
::dup2(saved, descriptor);
::close(saved);
}

bool write_descriptor(int descriptor, std::string_view bytes) {
std::size_t done = 0;
while (done < bytes.size()) {
Expand Down
15 changes: 14 additions & 1 deletion src/linux/arch/aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ long call(long n, long a = 0, long b = 0, long c = 0, long d = 0, long e = 0) {
return x0;
}

constexpr long nr_write = 64,
constexpr long nr_getrusage = 165,
nr_openat = 56,
nr_write = 64,
nr_close = 57,
nr_fcntl = 25,
nr_dup3 = 24,
Expand Down Expand Up @@ -50,6 +52,17 @@ long write(int fd, const void* bytes, unsigned long count) {

long close(int fd) { return call(nr_close, fd); }

long resource_usage(int who, long (&out)[18]) {
return call(nr_getrusage, who, reinterpret_cast<long>(out));
}

unsigned long maxrss_unit() { return 1024; } // kilobytes

long open_write(const char* path) {
// openat(AT_FDCWD, path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)
return call(nr_openat, -100, reinterpret_cast<long>(path), 01 | 0100 | 01000 | 02000000, 0600);
}

void default_signal(int signal) {
// The kernel's `struct sigaction', zero: SIG_DFL, no flags, nothing masked.
alignas(8) unsigned long long action[4] = {};
Expand Down
15 changes: 14 additions & 1 deletion src/linux/arch/riscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ long call(long n, long a = 0, long b = 0, long c = 0, long d = 0, long e = 0) {
return a0;
}

constexpr long nr_write = 64,
constexpr long nr_getrusage = 165,
nr_openat = 56,
nr_write = 64,
nr_close = 57,
nr_fcntl = 25,
nr_dup3 = 24,
Expand Down Expand Up @@ -50,6 +52,17 @@ long write(int fd, const void* bytes, unsigned long count) {

long close(int fd) { return call(nr_close, fd); }

long resource_usage(int who, long (&out)[18]) {
return call(nr_getrusage, who, reinterpret_cast<long>(out));
}

unsigned long maxrss_unit() { return 1024; } // kilobytes

long open_write(const char* path) {
// openat(AT_FDCWD, path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)
return call(nr_openat, -100, reinterpret_cast<long>(path), 01 | 0100 | 01000 | 02000000, 0600);
}

void default_signal(int signal) {
// The kernel's `struct sigaction', zero: SIG_DFL, no flags, nothing masked.
alignas(8) unsigned long long action[4] = {};
Expand Down
15 changes: 14 additions & 1 deletion src/linux/arch/x86_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ long call(long n, long a = 0, long b = 0, long c = 0, long d = 0, long e = 0) {
return r;
}

constexpr long nr_write = 1,
constexpr long nr_getrusage = 98,
nr_openat = 257,
nr_write = 1,
nr_close = 3,
nr_fcntl = 72,
nr_dup3 = 292,
Expand Down Expand Up @@ -50,6 +52,17 @@ long write(int fd, const void* bytes, unsigned long count) {

long close(int fd) { return call(nr_close, fd); }

long resource_usage(int who, long (&out)[18]) {
return call(nr_getrusage, who, reinterpret_cast<long>(out));
}

unsigned long maxrss_unit() { return 1024; } // kilobytes

long open_write(const char* path) {
// openat(AT_FDCWD, path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)
return call(nr_openat, -100, reinterpret_cast<long>(path), 01 | 0100 | 01000 | 02000000, 0600);
}

void default_signal(int signal) {
// The kernel's `struct sigaction', zero: SIG_DFL, no flags, nothing masked.
alignas(8) unsigned long long action[4] = {};
Expand Down
13 changes: 12 additions & 1 deletion src/macos/arch/aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ long call(long n, long a = 0, long b = 0, long c = 0, long d = 0, long e = 0) {
return failed ? -x0 : x0;
}

constexpr long nr_write = 4, nr_close = 6, nr_getpid = 20, nr_kill = 37, nr_sigaction = 46,
constexpr long nr_getrusage = 117, nr_open = 5, nr_write = 4, nr_close = 6, nr_getpid = 20, nr_kill = 37, nr_sigaction = 46,
nr_sigprocmask = 48, nr_dup2 = 90, nr_fcntl = 92, nr_statfs64 = 345, nr_renameatx_np = 488,
nr_geteuid = 25, nr_umask = 60;

Expand All @@ -44,6 +44,17 @@ long write(int fd, const void* bytes, unsigned long count) {

long close(int fd) { return call(nr_close, fd); }

long resource_usage(int who, long (&out)[18]) {
return call(nr_getrusage, who, reinterpret_cast<long>(out));
}

unsigned long maxrss_unit() { return 1; } // bytes

long open_write(const char* path) {
// open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)
return call(nr_open, reinterpret_cast<long>(path), 0x1 | 0x200 | 0x400 | 0x1000000, 0600);
}

void default_signal(int signal) {
// The kernel's `struct __sigaction' (handler, trampoline, mask, flags),
// zero: SIG_DFL, which the kernel enters no trampoline for.
Expand Down
13 changes: 12 additions & 1 deletion src/macos/arch/x86_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ long call(long n, long a = 0, long b = 0, long c = 0, long d = 0, long e = 0) {
return failed ? -r : r;
}

constexpr long nr_write = 4, nr_close = 6, nr_getpid = 20, nr_kill = 37, nr_sigaction = 46,
constexpr long nr_getrusage = 117, nr_open = 5, nr_write = 4, nr_close = 6, nr_getpid = 20, nr_kill = 37, nr_sigaction = 46,
nr_sigprocmask = 48, nr_dup2 = 90, nr_fcntl = 92, nr_statfs64 = 345, nr_renameatx_np = 488,
nr_geteuid = 25, nr_umask = 60;

Expand All @@ -41,6 +41,17 @@ long write(int fd, const void* bytes, unsigned long count) {

long close(int fd) { return call(nr_close, fd); }

long resource_usage(int who, long (&out)[18]) {
return call(nr_getrusage, who, reinterpret_cast<long>(out));
}

unsigned long maxrss_unit() { return 1; } // bytes

long open_write(const char* path) {
// open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)
return call(nr_open, reinterpret_cast<long>(path), 0x1 | 0x200 | 0x400 | 0x1000000, 0600);
}

void default_signal(int signal) {
// The kernel's `struct __sigaction' (handler, trampoline, mask, flags),
// zero: SIG_DFL, which the kernel enters no trampoline for.
Expand Down
4 changes: 4 additions & 0 deletions src/openkal/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ result<void> replace_image(const std::filesystem::path&, const std::vector<std::
return std::unexpected(error{ errc::not_supported, "this system replaces no program's image" });
}

result<usage> resources(of) {
return std::unexpected(error{ errc::not_supported, "this system tells no program's use of its resources" });
}

} // namespace mcpplibs::os::process::sys
2 changes: 2 additions & 0 deletions src/openkal/stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ namespace mcpplibs::os::stdio::sys {
int divert_output() { return -1; }
void restore_output(int) {}
bool write_descriptor(int, std::string_view) { return false; }
int divert_to_file(int, const std::filesystem::path&) { return -1; }
void restore_descriptor(int, int) {}
} // namespace mcpplibs::os::stdio::sys
22 changes: 22 additions & 0 deletions src/posix/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ import mcpplibs.os.platform;

namespace mcpplibs::os::process::sys {

result<usage> resources(of who) {
long r[18] = {};
if (kernel::resource_usage(who == of::self ? 0 : -1, r) < 0)
return std::unexpected(error{ errc::io, "asking the kernel what was used" });
usage u;
u.user = std::chrono::seconds(r[0]) + std::chrono::microseconds(r[1]);
u.system = std::chrono::seconds(r[2]) + std::chrono::microseconds(r[3]);
u.peak_memory = static_cast<std::uint64_t>(r[4]) * kernel::maxrss_unit();
// The bytes moved by reads and writes, where the kernel counts them for
// this process: /proc/self/io on Linux. The children's are not counted.
if (who == of::self)
if (std::ifstream io("/proc/self/io"); io) {
std::string key;
std::uint64_t value = 0;
while (io >> key >> value) {
if (key == "rchar:") u.read_bytes = value;
else if (key == "wchar:") u.written_bytes = value;
}
}
return u;
}

result<void> replace_image(const std::filesystem::path& program,
const std::vector<std::string>& argv,
const std::vector<std::string>& environment,
Expand Down
8 changes: 8 additions & 0 deletions src/posix/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ long dup_above(int fd, int lowest);
long dup_onto(int from, int to);
long write(int fd, const void* bytes, unsigned long count);
long close(int fd);
// A new descriptor for writing the file `path' names, created (mode 0600) or
// emptied, closed on exec.
long open_write(const char* path);
// The kernel's `struct rusage' of this process (`who' 0) or of its waited-for
// children (`who' -1): two timevals and fourteen longs, as on every 64-bit
// Linux and macOS. `ru_maxrss' is in units of `maxrss_unit()' bytes.
long resource_usage(int who, long (&out)[18]);
unsigned long maxrss_unit();
// The signal at its default disposition.
void default_signal(int signal);
// The signal unblocked in this thread and sent to this process.
Expand Down
18 changes: 18 additions & 0 deletions src/posix/stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ void restore_output(int saved) {
k::close(saved);
}

int divert_to_file(int descriptor, const std::filesystem::path& file) {
const long target = k::open_write(file.c_str());
if (target < 0) return -1;
const long saved = k::dup_above(descriptor, 3);
if (saved < 0) {
k::close(static_cast<int>(target));
return -1;
}
k::dup_onto(static_cast<int>(target), descriptor);
k::close(static_cast<int>(target));
return static_cast<int>(saved);
}

void restore_descriptor(int descriptor, int saved) {
k::dup_onto(saved, descriptor);
k::close(saved);
}

bool write_descriptor(int descriptor, std::string_view bytes) {
std::size_t done = 0;
while (done < bytes.size()) {
Expand Down
2 changes: 2 additions & 0 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,8 @@ result<program> self() {
return program{ info.id, info.started_ns };
}

result<usage> resources(of who) { return sys::resources(who); }

result<program_info> describe(std::uint64_t id) {
kal_process p{};
if (const int rc = kal_process_open_id(id, &p); rc != kal_ok)
Expand Down
18 changes: 18 additions & 0 deletions src/process.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,22 @@ struct program_info {
bool ended = false;
};

// WHAT A PROGRAM HAS USED (0.4.1): processor time, the largest resident set and
// the bytes moved by its reads and writes. `of::self' is this program so far;
// `of::children' the programs it started, waited for and whose handles it has
// closed (which reaps them), together, as the kernel accounts them (Linux and
// macOS). A runner that starts one program and
// asks for its children has that program's use. A field the system does not
// tell is absent; errc::not_supported where nothing is told (an image, the Web,
// a Windows program's children).
enum class of { self, children };
struct usage {
std::chrono::nanoseconds user{0}, system{0};
std::optional<std::uint64_t> peak_memory; // bytes
std::optional<std::uint64_t> read_bytes, written_bytes;
};
result<usage> resources(of who = of::self);

// This program.
result<program> self();
// What is known of the program with this identifier now; errc::not_found
Expand Down Expand Up @@ -283,4 +299,6 @@ result<void> replace_image(const std::filesystem::path& program,
const std::vector<std::string>& argv,
const std::vector<std::string>& environment,
const std::filesystem::path& cwd);
// What `resources' reports, asked of the system (src/<os>/exec.cpp).
result<usage> resources(of who);
} // namespace mcpplibs::os::process::sys
47 changes: 47 additions & 0 deletions src/stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,53 @@ output_to_error::~output_to_error() {
sys::restore_output(saved_);
}

result<capture> capture::begin(stream which) {
std::fflush(nullptr);
(void)flush_output();
capture c;
c.descriptor_ = which == stream::output ? 1 : 2;
c.file_ = std::filesystem::temp_directory_path()
/ std::format("mcpplibs-os-capture-{}-{:x}", c.descriptor_, std::random_device{}());
c.saved_ = sys::divert_to_file(c.descriptor_, c.file_);
if (c.saved_ < 0) {
std::error_code ec;
std::filesystem::remove(c.file_, ec);
return std::unexpected(error{ errc::not_supported, "keeping what a standard stream is written" });
}
return c;
}

capture::capture(capture&& o) noexcept
: descriptor_(std::exchange(o.descriptor_, -1)), saved_(std::exchange(o.saved_, -1)), file_(std::move(o.file_)) {}
capture& capture::operator=(capture&& o) noexcept {
if (this != &o) {
end();
descriptor_ = std::exchange(o.descriptor_, -1);
saved_ = std::exchange(o.saved_, -1);
file_ = std::move(o.file_);
}
return *this;
}
capture::~capture() { end(); }

void capture::end() {
if (saved_ < 0) return;
std::fflush(nullptr);
(void)flush_output();
sys::restore_descriptor(descriptor_, std::exchange(saved_, -1));
}

std::string capture::text() {
if (file_.empty()) return {};
end();
std::string kept;
if (std::ifstream in(file_, std::ios::binary); in) kept.assign(std::istreambuf_iterator<char>(in), {});
std::error_code ec;
std::filesystem::remove(file_, ec);
file_.clear();
return kept;
}

result<void> output_to_error::write_original(std::string_view bytes) {
if (saved_ < 0) return write_output(bytes);
if (!sys::write_descriptor(saved_, bytes)) return std::unexpected(error{ errc::io, "writing standard output" });
Expand Down
Loading
Loading