Actually implement event loop & process stuff
Pretty easy all things considered.
This commit is contained in:
parent
dd0280a1e5
commit
833f2054a4
|
@ -21,6 +21,7 @@ BinPackParameters: true
|
||||||
BreakConstructorInitializers: BeforeColon
|
BreakConstructorInitializers: BeforeColon
|
||||||
BreakStringLiterals: false
|
BreakStringLiterals: false
|
||||||
|
|
||||||
|
ColumnLimit: 0
|
||||||
CompactNamespaces: false
|
CompactNamespaces: false
|
||||||
|
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
project(nanosm
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
include(Policies)
|
||||||
|
include(ProjectFuncs)
|
||||||
|
include(CompilerFlags)
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(nanosm
|
||||||
|
src/main.cpp
|
||||||
|
|
||||||
|
src/WordExp.cpp
|
||||||
|
|
||||||
|
src/EventLoop.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
nanosm_target(nanosm)
|
||||||
|
|
||||||
|
# todo: cmake install sex
|
|
@ -0,0 +1,67 @@
|
||||||
|
# TODO: This currently assumes libstdc++, later on we should *probably* set this with some detection
|
||||||
|
# also TODO: Use a list so that this isn't one giant line (list JOIN should help.)
|
||||||
|
set(NANOSM_CORE_COMPILE_ARGS "-Wall -Wformat=2 -Wimplicit-fallthrough -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -fstrict-flex-arrays=3 -fstack-clash-protection -fstack-protector-strong")
|
||||||
|
set(NANOSM_CORE_LINKER_ARGS "-fuse-ld=${NANOSM_LINKER}")
|
||||||
|
|
||||||
|
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"
|
||||||
|
# If on Release use link-time optimizations.
|
||||||
|
# On clang we use ThinLTO for even better build performance.
|
||||||
|
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||||
|
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -flto=thin")
|
||||||
|
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -flto=thin")
|
||||||
|
else()
|
||||||
|
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -flto")
|
||||||
|
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -flto")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(_NANOSM_CORE_WANTED_SANITIZERS "")
|
||||||
|
|
||||||
|
if("asan" IN_LIST NANOSM_SANITIZERS)
|
||||||
|
# Error if someone's trying to mix asan and tsan together since they aren't compatible.
|
||||||
|
if("tsan" IN_LIST NANOSM_SANITIZERS)
|
||||||
|
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Enabling ASAN because it was in NANOSM_SANITIZERS")
|
||||||
|
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "address")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("tsan" IN_LIST NANOSM_SANITIZERS)
|
||||||
|
if("asan" IN_LIST NANOSM_SANITIZERS)
|
||||||
|
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Enabling TSAN because it was in NANOSM_SANITIZERS")
|
||||||
|
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "thread")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("ubsan" IN_LIST NANOSM_SANITIZERS)
|
||||||
|
message(STATUS "Enabling UBSAN because it was in NANOSM_SANITIZERS")
|
||||||
|
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "undefined")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(LENGTH _NANOSM_CORE_WANTED_SANITIZERS _NANOSM_CORE_WANTED_SANITIZERS_LENGTH)
|
||||||
|
if(NOT _NANOSM_CORE_WANTED_SANITIZERS_LENGTH EQUAL 0)
|
||||||
|
list(JOIN _NANOSM_CORE_WANTED_SANITIZERS "," _NANOSM_CORE_WANTED_SANITIZERS_ARG)
|
||||||
|
message(STATUS "Enabled sanitizers: ${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||||
|
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -fsanitize=${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||||
|
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -fsanitize=${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Set core CMake toolchain variables so that they get applied to all projects.
|
||||||
|
# A bit nasty, but /shrug, this way our third party libraries can be mostly sanitized/etc as well.
|
||||||
|
# We do NOT do this for CMake compiler features however.
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS "${NANOSM_CORE_COMPILE_ARGS}")
|
||||||
|
set(CMAKE_CXX_FLAGS "${NANOSM_CORE_COMPILE_ARGS}")
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -O0 -g3")
|
||||||
|
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -O3 -g3")
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g3")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -O3 -g3")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3")
|
||||||
|
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${NANOSM_CORE_LINKER_ARGS} -Wl,-z,noexecstack,-z,relro,-z,now")
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Core compile arguments used for the whole project
|
||||||
|
#
|
||||||
|
# This is the driver, we include compiler/platform specific files here
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
include(CompilerFlags-GNU)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Unsupported (for now?) compiler ${CMAKE_CXX_COMPILER_ID}")
|
||||||
|
endif()
|
|
@ -0,0 +1,22 @@
|
||||||
|
# CMake policy configuration
|
||||||
|
|
||||||
|
# Macro to enable new CMake policy.
|
||||||
|
# Makes this file a *LOT* shorter.
|
||||||
|
macro (_new_cmake_policy policy)
|
||||||
|
if(POLICY ${policy})
|
||||||
|
#message(STATUS "Enabling new policy ${policy}")
|
||||||
|
cmake_policy(SET ${policy} NEW)
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
_new_cmake_policy(CMP0026) # CMake 3.0: Disallow use of the LOCATION property for build targets.
|
||||||
|
_new_cmake_policy(CMP0042) # CMake 3.0+ (2.8.12): MacOS "@rpath" in target's install name
|
||||||
|
_new_cmake_policy(CMP0046) # warn about non-existent dependencies
|
||||||
|
_new_cmake_policy(CMP0048) # CMake 3.0+: project() command now maintains VERSION
|
||||||
|
_new_cmake_policy(CMP0054) # CMake 3.1: Only interpret if() arguments as variables or keywords when unquoted.
|
||||||
|
_new_cmake_policy(CMP0056) # try_compile() linker flags
|
||||||
|
_new_cmake_policy(CMP0066) # CMake 3.7: try_compile(): use per-config flags, like CMAKE_CXX_FLAGS_RELEASE
|
||||||
|
_new_cmake_policy(CMP0067) # CMake 3.8: try_compile(): honor language standard variables (like C++11)
|
||||||
|
_new_cmake_policy(CMP0068) # CMake 3.9+: `RPATH` settings on macOS do not affect `install_name`.
|
||||||
|
_new_cmake_policy(CMP0075) # CMake 3.12+: Include file check macros honor `CMAKE_REQUIRED_LIBRARIES`
|
||||||
|
_new_cmake_policy(CMP0077) # CMake 3.13+: option() honors normal variables.
|
|
@ -0,0 +1,26 @@
|
||||||
|
function(nanosm_target target)
|
||||||
|
target_compile_definitions(${target} PRIVATE "$<$<CONFIG:DEBUG>:NANOSM_DEBUG>")
|
||||||
|
target_compile_features(${target} PUBLIC cxx_std_20)
|
||||||
|
target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(nanosm_set_alternate_linker)
|
||||||
|
find_program(LINKER_EXECUTABLE ld.${NANOSM_LINKER} ${NANOSM_LINKER})
|
||||||
|
if(LINKER_EXECUTABLE)
|
||||||
|
message(STATUS "Using ${NANOSM_LINKER} as argument to -fuse-ld=")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Linker ${NANOSM_LINKER} does not exist on your system. Please specify one which does or omit this option from your configure command.")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# set the default linker based on compiler id, if one is not provided
|
||||||
|
# This is provided so that it can be overridden
|
||||||
|
if(NOT NANOSM_LINKER AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||||
|
set(NANOSM_LINKER "lld")
|
||||||
|
elseif(NOT NANOSM_LINKER)
|
||||||
|
set(NANOSM_LINKER "bfd")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
nanosm_set_alternate_linker()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
[nanosm]
|
[nanosm]
|
||||||
# The window manager you want to use. This is the first application
|
# The window manager you want to use. This is the first application
|
||||||
# launched, and this will always be true
|
# launched
|
||||||
window-manager="/path/to/wm/binary --any-additional-args-here"
|
window-manager="/path/to/wm/binary --any-additional-args-here"
|
||||||
|
|
||||||
# Enable verbose debug logging. Only useful for debugging issues.
|
# Enable verbose debug logging. Only useful for debugging issues.
|
||||||
|
@ -12,7 +12,9 @@ verbose=false
|
||||||
restart-delay=1
|
restart-delay=1
|
||||||
|
|
||||||
# Any applications besides your window manager you want to run at startup.
|
# Any applications besides your window manager you want to run at startup.
|
||||||
# Note that applications are executed in the order they are declared.
|
# Note that applications are executed in the order they are declared, but
|
||||||
|
# this will not hold true if any (or all apps) crash (they will be restarted
|
||||||
|
# effectively in a psuedorandom order).
|
||||||
[nanosm.apps]
|
[nanosm.apps]
|
||||||
lxpanel = { command = "lxpanel" }
|
lxpanel = { command = "lxpanel" }
|
||||||
pcmanfm-desktop = { command = "pcmanfm --desktop" }
|
pcmanfm-desktop = { command = "pcmanfm --desktop" }
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
#include "EventLoop.hpp"
|
||||||
|
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
namespace nanosm {
|
||||||
|
|
||||||
|
/// A little ergonomic wrapper over
|
||||||
|
/// std::unique_ptr<T[]>, for a "kinda-vector"
|
||||||
|
/// that lives on the heap and is statically sized
|
||||||
|
template <class T>
|
||||||
|
struct UniqueArray final {
|
||||||
|
explicit UniqueArray(usize size)
|
||||||
|
: array(std::make_unique<T[]>(size)),
|
||||||
|
size(size) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueArray(UniqueArray&& move) {
|
||||||
|
array = std::move(move.array);
|
||||||
|
size = move.size;
|
||||||
|
|
||||||
|
// invalidate
|
||||||
|
move.array = nullptr;
|
||||||
|
move.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator[](usize index) { return Get()[index]; }
|
||||||
|
const T& operator[](usize index) const { return Get()[index]; }
|
||||||
|
|
||||||
|
T* Get() { return array.get(); }
|
||||||
|
const T* Get() const { return array.get(); }
|
||||||
|
usize Size() const { return size; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<T[]> array {};
|
||||||
|
usize size {};
|
||||||
|
};
|
||||||
|
|
||||||
|
EventLoop::EventLoop() {
|
||||||
|
epollFd = epoll_create1(EPOLL_CLOEXEC);
|
||||||
|
|
||||||
|
if(epollFd == -1) {
|
||||||
|
perror("You Banned From Epoll, Rules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventLoop::~EventLoop() {
|
||||||
|
close(epollFd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventLoop::Post(PostFn func) {
|
||||||
|
if(!func)
|
||||||
|
return;
|
||||||
|
postCallbacks.push_back(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventLoop::AddObject(IoObject::Ptr obj) {
|
||||||
|
if(!obj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(obj->GetFD() == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ioObjects.insert(obj);
|
||||||
|
|
||||||
|
epoll_event ev {};
|
||||||
|
|
||||||
|
ev.events = obj->InterestedEvents();
|
||||||
|
ev.data.fd = obj->GetFD();
|
||||||
|
|
||||||
|
epoll_ctl(epollFd, EPOLL_CTL_ADD, obj->GetFD(), &ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventLoop::RemoveObject(IoObject::Ptr obj) {
|
||||||
|
if(!obj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ioObjects.erase(obj);
|
||||||
|
epoll_event ev {};
|
||||||
|
|
||||||
|
ev.events = obj->InterestedEvents();
|
||||||
|
ev.data.fd = obj->GetFD();
|
||||||
|
|
||||||
|
epoll_ctl(epollFd, EPOLL_CTL_DEL, obj->GetFD(), &ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventLoop::Run() {
|
||||||
|
UniqueArray<epoll_event> events { 16 };
|
||||||
|
|
||||||
|
while(!shouldStop) {
|
||||||
|
auto nevents = epoll_wait(epollFd, events.Get(), events.Size(), 10);
|
||||||
|
if(nevents == -1) {
|
||||||
|
perror("epoll_wait");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All OK, let's check for events now
|
||||||
|
|
||||||
|
for(int i = 0; i < nevents; ++i) {
|
||||||
|
for(auto pollable : ioObjects) {
|
||||||
|
if(auto fd = pollable->GetFD(); fd != -1) {
|
||||||
|
// Signal any events that occur for this
|
||||||
|
if(events[i].data.fd == fd)
|
||||||
|
pollable->OnReady(events[i].events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the topmost callback once every event loop iteration.
|
||||||
|
if(!postCallbacks.empty()) {
|
||||||
|
auto& frontCallback = postCallbacks.front();
|
||||||
|
frontCallback();
|
||||||
|
postCallbacks.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventLoop::Stop() {
|
||||||
|
shouldStop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nanosm
|
|
@ -1,34 +1,68 @@
|
||||||
#include <memory>
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "Types.hpp"
|
||||||
|
|
||||||
namespace nanosm {
|
namespace nanosm {
|
||||||
|
|
||||||
/// The nanosm event loop. Pretty barren.
|
/// The nanosm event loop. Not that barren, but basically
|
||||||
|
/// just enough to have the program working.
|
||||||
struct EventLoop {
|
struct EventLoop {
|
||||||
|
using PostFn = std::function<void()>;
|
||||||
|
|
||||||
/// A pollable object.
|
/// A IO object.
|
||||||
struct Pollable {
|
struct IoObject {
|
||||||
virtual ~Pollable() = default;
|
using Ptr = std::shared_ptr<IoObject>;
|
||||||
|
|
||||||
/// Returns the FD. May return nullopt if there is no active file descriptor
|
explicit IoObject(EventLoop& assoc)
|
||||||
/// for this polled object (this simply means you won't get events until there is one)
|
: eventLoop(assoc) {
|
||||||
virtual std::optional<int> GetFD() const = 0;
|
}
|
||||||
|
|
||||||
|
virtual ~IoObject() = default;
|
||||||
|
|
||||||
|
/// Returns the file descriptor for this IO object.
|
||||||
|
virtual int GetFD() const = 0;
|
||||||
|
|
||||||
|
/// Return raw event mask
|
||||||
|
virtual int InterestedEvents() const = 0;
|
||||||
|
|
||||||
/// Called when the object is ready (do any i/o or handling here)
|
/// Called when the object is ready (do any i/o or handling here)
|
||||||
virtual void OnReady() = 0;
|
/// [eventMask] is the raw epoll event mask
|
||||||
|
virtual void OnReady(int eventMask) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
EventLoop& eventLoop;
|
||||||
};
|
};
|
||||||
|
|
||||||
EventLoop();
|
EventLoop();
|
||||||
~EventLoop();
|
~EventLoop();
|
||||||
|
|
||||||
/// Add an object to the epoll event loop.
|
/// Add an object to the epoll event loop.
|
||||||
void AddObject(int fd, std::shared_ptr<Pollable> obj);
|
void AddObject(IoObject::Ptr obj);
|
||||||
|
|
||||||
|
void RemoveObject(IoObject::Ptr obj);
|
||||||
|
|
||||||
|
/// Posts a function to run after the epoll events are dispatched.
|
||||||
|
/// This queue of functions is mutually exclusive, so is also usable by
|
||||||
|
/// I/O objects to post completion notifications/callbacks.
|
||||||
|
void Post(PostFn func);
|
||||||
|
|
||||||
/// Runs the main loop.
|
/// Runs the main loop.
|
||||||
void Run();
|
void Run();
|
||||||
|
|
||||||
private:
|
void Stop();
|
||||||
int epollFd{};
|
|
||||||
|
private:
|
||||||
|
int epollFd {};
|
||||||
|
bool shouldStop { false };
|
||||||
|
|
||||||
|
std::set<std::shared_ptr<IoObject>> ioObjects {};
|
||||||
|
std::deque<PostFn> postCallbacks {};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace nanosm
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using i8 = std::int8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using i16 = std::int16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
using i64 = std::int64_t;
|
||||||
|
using usize = std::size_t;
|
||||||
|
using isize = std::intptr_t;
|
|
@ -0,0 +1,57 @@
|
||||||
|
#include "WordExp.hpp"
|
||||||
|
|
||||||
|
#include "Types.hpp"
|
||||||
|
|
||||||
|
namespace nanosm {
|
||||||
|
WordExp WordExp::Expand(std::string_view string) {
|
||||||
|
WordExp res {};
|
||||||
|
|
||||||
|
usize startIndex {};
|
||||||
|
usize endIndex {};
|
||||||
|
bool inQuotes { false };
|
||||||
|
|
||||||
|
auto len = [&]() { return (endIndex - startIndex); };
|
||||||
|
// auto currentWord = [&]() { return std::string(string.data() + startIndex, len()); };
|
||||||
|
auto addWord = [&](auto nextIndex) {
|
||||||
|
// a bit of a HACK. should be fixed properly, I think
|
||||||
|
if(len() > 1)
|
||||||
|
res.words.emplace_back(string.data() + startIndex, len());
|
||||||
|
|
||||||
|
startIndex = nextIndex;
|
||||||
|
endIndex = startIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
for(usize i = 0; i < string.length(); ++i) {
|
||||||
|
switch(string[i]) {
|
||||||
|
case '"':
|
||||||
|
if(!inQuotes) {
|
||||||
|
inQuotes = true;
|
||||||
|
startIndex = i + 1;
|
||||||
|
} else {
|
||||||
|
inQuotes = false;
|
||||||
|
addWord(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
endIndex++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ' ':
|
||||||
|
// Spaces are allowed in quoted strings
|
||||||
|
if(!inQuotes)
|
||||||
|
addWord(i + 1);
|
||||||
|
else
|
||||||
|
endIndex++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: endIndex++; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last word
|
||||||
|
if(len() != 0)
|
||||||
|
addWord(0);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nanosm
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace nanosm {
|
||||||
|
|
||||||
|
/// A kind-of reimplementation of wordexp(3) in C++, since it's rife with security issues,
|
||||||
|
/// annoying, and very poorly implemented (one implementation actually
|
||||||
|
/// ends up spawning a shell, that end up running Perl code. I'm not kidding.)
|
||||||
|
struct WordExp {
|
||||||
|
std::vector<std::string> words;
|
||||||
|
|
||||||
|
/// Expand a string (e.g "hello world \"testing 1234\"") into individual parts.
|
||||||
|
/// This function also respects quotation marks.
|
||||||
|
///
|
||||||
|
/// This function does NOT:
|
||||||
|
/// - Expand environment strings (e.g: $PWD or etc.)
|
||||||
|
/// - Expand shell ~~injection~~ strings (eg `uname -r`)
|
||||||
|
///
|
||||||
|
/// For the sample input, the returned object's `words` vector would look like:
|
||||||
|
/// [0]: hello
|
||||||
|
/// [1]: world
|
||||||
|
/// [2]: testing 1234
|
||||||
|
static WordExp Expand(std::string_view string);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nanosm
|
191
src/main.cpp
191
src/main.cpp
|
@ -1,7 +1,196 @@
|
||||||
|
#include <bits/types/struct_itimerspec.h>
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "EventLoop.hpp"
|
#include "EventLoop.hpp"
|
||||||
|
#include "WordExp.hpp"
|
||||||
|
|
||||||
|
nanosm::EventLoop ev;
|
||||||
|
|
||||||
|
#include <linux/sched.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
struct TestProcess : nanosm::EventLoop::IoObject, std::enable_shared_from_this<TestProcess> {
|
||||||
|
// 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<u64>(&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<char*> 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<void(int)> 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<void(int)> onProcessExit;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <sys/timerfd.h>
|
||||||
|
|
||||||
|
struct TestTimer : nanosm::EventLoop::IoObject, std::enable_shared_from_this<TestTimer> {
|
||||||
|
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<void()> 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<void()> cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
// tests stuff :)
|
||||||
|
|
||||||
|
auto timer = std::make_shared<TestTimer>(ev);
|
||||||
|
|
||||||
|
void test() {
|
||||||
|
auto process = std::make_shared<TestProcess>(ev);
|
||||||
|
|
||||||
|
// Do magic
|
||||||
|
process->SetExitCallback([p = process](int exitCode) {
|
||||||
|
printf("exited with %d exitcode\n", exitCode);
|
||||||
|
|
||||||
|
timer->SetExpiryCallback([pp = p]() {
|
||||||
|
printf("5s elapsed, restarting Nowr\n");
|
||||||
|
pp->Respawn();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the timer to wait a bit before restarting the process
|
||||||
|
timer->Arm(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.AddObject(timer);
|
||||||
|
process->Spawn("xterm");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
nanosm::EventLoop ev;
|
ev.Post(test);
|
||||||
|
|
||||||
ev.Run();
|
ev.Run();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue