diff --git a/CMakeLists.txt b/CMakeLists.txt index d553d38..88eb805 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,9 +14,11 @@ include(CompilerFlags) add_executable(nanosm src/main.cpp - src/WordExp.cpp - + # glue code src/EventLoop.cpp + src/Timer.cpp + src/WordExp.cpp + src/Process.cpp ) nanosm_target(nanosm) diff --git a/src/EventLoop.hpp b/src/EventLoop.hpp index 8278104..cbf5a5e 100644 --- a/src/EventLoop.hpp +++ b/src/EventLoop.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/src/Process.cpp b/src/Process.cpp new file mode 100644 index 0000000..4f6d352 --- /dev/null +++ b/src/Process.cpp @@ -0,0 +1,94 @@ +#include "Process.hpp" + +#include +#include +#include +#include + +#include "WordExp.hpp" + +namespace nanosm { + + inline static pid_t Clone3(const clone_args& args) { + return syscall(SYS_clone3, &args, sizeof(clone_args)); + } + + Process::~Process() { + Kill(); + Reset(); + } + + int Process::GetFD() const { + return pidFd; + } + + int Process::InterestedEvents() const { + /// Pidfd only returns EPOLLIN. + return EPOLLIN; + } + + void Process::Spawn(const std::string& commandLine) { + commLine = commandLine; + Respawn(); + } + + void Process::Respawn() { + pid = Clone3({ + .flags = CLONE_PIDFD, + .pidfd = std::bit_cast(&pidFd), + .exit_signal = SIGCHLD, + }); + + if(pid < 0) + perror("Clone3()"); + + if(pid == 0) { + // Forked from the parent successfully, execute the given thing + auto exp = nanosm::WordExp::Expand(commLine); + std::vector argv(exp.words.size()); + for(usize i = 0; i < exp.words.size(); ++i) + argv[i] = exp.words[i].data(); + + execvp(exp.words[0].data(), argv.data()); + } else { + // Parent: monitor FD + eventLoop.AddObject(IoObject::Ptr(shared_from_this())); + } + } + + void Process::OnReady(int eventMask) { + // In our case, any readiness signaled by the pidfd means the process exited + // so this will never block (or really, wait). + waitid(P_PIDFD, pidFd, &siginfo, WNOHANG); + + // Post a callback to call the user's exit callback (if one exists) + eventLoop.Post([self = shared_from_this()]() { + // Prepare for re-attaching, or etc. + self->eventLoop.RemoveObject(self); + self->Reset(); + + if(self->onProcessExit) + self->onProcessExit(self->siginfo.si_status); + }); + } + + void Process::Kill() { + if(pid != -1) + kill(pid, SIGTERM); + } + + void Process::SetExitCallback(std::function f) { + onProcessExit = f; + } + + void Process::Reset() { + if(pidFd != -1) { + close(pidFd); + pidFd = -1; + } + + pid = -1; + siginfo = {}; + } + +} // namespace nanosm diff --git a/src/Process.hpp b/src/Process.hpp new file mode 100644 index 0000000..3437c3d --- /dev/null +++ b/src/Process.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "EventLoop.hpp" + +namespace nanosm { + + struct Process : nanosm::EventLoop::IoObject, std::enable_shared_from_this { + // bring ctor in + using nanosm::EventLoop::IoObject::IoObject; + + virtual ~Process(); + + int GetFD() const override; + + int InterestedEvents() const override; + + void Spawn(const std::string& commandLine); + + void Respawn(); + + void OnReady(int eventMask) override; + + void Kill(); + + void SetExitCallback(std::function f); + + private: + void Reset(); + + int pidFd { -1 }; + pid_t pid { -1 }; + siginfo_t siginfo {}; + std::string commLine; + + std::function onProcessExit; + }; + +} // namespace nanosm diff --git a/src/Timer.cpp b/src/Timer.cpp new file mode 100644 index 0000000..e395d18 --- /dev/null +++ b/src/Timer.cpp @@ -0,0 +1,55 @@ +#include "Timer.hpp" + +#include + +namespace nanosm { + + Timer::Timer(nanosm::EventLoop& ev) + : nanosm::EventLoop::IoObject(ev) { + timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if(timerFd < 0) + perror("timerfd_create"); + } + + Timer::~Timer() { + Reset(); + } + + void Timer::Reset() { + if(timerFd != -1) + close(timerFd); + } + + int Timer::GetFD() const { + return timerFd; + } + + int Timer::InterestedEvents() const { + /// TimerFD only returns EPOLLIN. + return EPOLLIN; + } + + void Timer::OnReady(int eventMask) { + u64 expiryCount {}; + + // Read the amount of timer expires so it stops screaming. + static_cast(read(timerFd, &expiryCount, sizeof(expiryCount))); + + // Post the expiry callback directly into the event loop + if(cb) + eventLoop.Post(cb); + } + + void Timer::SetExpiryCallback(std::function expire) { + cb = expire; + } + + void Timer::Arm(u32 durationSeconds) { + itimerspec spec {}; + spec.it_value.tv_sec = durationSeconds; + + // TODO: validate. + timerfd_settime(timerFd, 0, &spec, nullptr); + } + +} // namespace nanosm diff --git a/src/Timer.hpp b/src/Timer.hpp new file mode 100644 index 0000000..4ee37d1 --- /dev/null +++ b/src/Timer.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "EventLoop.hpp" + +namespace nanosm { + + struct Timer : nanosm::EventLoop::IoObject, std::enable_shared_from_this { + Timer(nanosm::EventLoop& ev); + + virtual ~Timer(); + + void Reset(); + int GetFD() const override; + + int InterestedEvents() const override; + + void OnReady(int eventMask) override; + + void SetExpiryCallback(std::function expire); + void Arm(u32 durationSeconds); + private: + int timerFd { -1 }; + std::function cb; + }; + +} // namespace nanosm diff --git a/src/main.cpp b/src/main.cpp index 3777542..e234212 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include + #include #include @@ -6,171 +6,17 @@ #include "EventLoop.hpp" #include "WordExp.hpp" +#include "Timer.hpp" +#include "Process.hpp" + nanosm::EventLoop ev; -#include -#include -#include -#include -#include - -struct TestProcess : nanosm::EventLoop::IoObject, std::enable_shared_from_this { - // bring ctor in - using nanosm::EventLoop::IoObject::IoObject; - - virtual ~TestProcess() { - Kill(); - Reset(); - } - - int GetFD() const override { - return pidfdFd; - } - - int InterestedEvents() const override { - /// Pidfd only returns EPOLLIN. - return EPOLLIN; - } - - void Spawn(const std::string& commandLine) { - commLine = commandLine; - Respawn(); - } - - void Respawn() { - pid = Clone3({ - .flags = CLONE_PIDFD, - .pidfd = std::bit_cast(&pidfdFd), - .exit_signal = SIGCHLD, - }); - - if(pid < 0) - perror("Error cloning"); - - if(pid == 0) { - // Forked from the parent successfully, execute the given thing - auto exp = nanosm::WordExp::Expand(commLine); - std::vector argv(exp.words.size()); - for(usize i = 0; i < exp.words.size(); ++i) - argv[i] = exp.words[i].data(); - - execvp(exp.words[0].data(), argv.data()); - } else { - // Parent: monitor FD - eventLoop.AddObject(IoObject::Ptr(shared_from_this())); - } - } - - void OnReady(int eventMask) override { - // In our case, any readiness signaled by the pidfd means the process exited - // so this will never block (or really, wait). - waitid(P_PIDFD, pidfdFd, &siginfo, WNOHANG); - - // Post a callback to call the user's - eventLoop.Post([self = shared_from_this()]() { - // Prepare for re-attaching, or etc. - self->eventLoop.RemoveObject(self); - self->Reset(); - - if(self->onProcessExit) - self->onProcessExit(self->siginfo.si_status); - }); - } - - void Kill() { - if(pid != -1) - kill(pid, SIGTERM); - } - - void SetExitCallback(std::function f) { - onProcessExit = f; - } - - private: - static pid_t Clone3(const clone_args& args) { - return syscall(SYS_clone3, &args, sizeof(clone_args)); - } - - void Reset() { - if(pidfdFd != -1) { - close(pidfdFd); - pidfdFd = -1; - } - - pid = -1; - siginfo = {}; - } - - int pidfdFd { -1 }; - pid_t pid { -1 }; - siginfo_t siginfo {}; - std::string commLine; - - std::function onProcessExit; -}; - -#include - -struct TestTimer : nanosm::EventLoop::IoObject, std::enable_shared_from_this { - TestTimer(nanosm::EventLoop& ev) - : nanosm::EventLoop::IoObject(ev) { - timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); - if(timerFd < 0) - perror("timerfd_create"); - } - - virtual ~TestTimer() { - Reset(); - } - - void Reset() { - if(timerFd != -1) - close(timerFd); - } - - int GetFD() const override { - return timerFd; - } - - int InterestedEvents() const override { - /// TimerFD only returns EPOLLIN. - return EPOLLIN; - } - - void OnReady(int eventMask) override { - u64 expiryCount {}; - - // Read the amount of timer expires so it stops screaming. - read(timerFd, &expiryCount, sizeof(expiryCount)); - - // Post the expiry callback directly into the event loop - if(cb) - eventLoop.Post(cb); - } - - void SetExpiryCallback(std::function expire) { - cb = expire; - } - - void Arm(u32 durationSeconds) { - itimerspec spec {}; - spec.it_value.tv_sec = durationSeconds; - - // TODO: validate. - timerfd_settime(timerFd, 0, &spec, nullptr); - } - - private: - int timerFd { -1 }; - std::function cb; -}; - // tests stuff :) -auto timer = std::make_shared(ev); +auto timer = std::make_shared(ev); +auto process = std::make_shared(ev); void test() { - auto process = std::make_shared(ev); // Do magic process->SetExitCallback([p = process](int exitCode) {