diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..72faecb --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,44 @@ +on: + push: + pull_request: + workflow_dispatch: +jobs: + build: + name: Build and package + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: Build package + uses: toltec-dev/build-action@v1 + - name: Save packages + uses: actions/upload-artifact@v3 + with: + name: packages + path: dist/rm*/*.ipk + - name: Save repo + uses: actions/upload-artifact@v3 + with: + name: rmall + path: dist/rmall + test: + name: Test package + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/download-artifact@v3 + id: download + with: + name: rmall + - uses: Eeems-Org/run-in-remarkable-action@v1 + with: + setup: | + set -ex + echo "src/gz local-rmall file:///opt/tmp/src" > /opt/etc/opkg.conf.d/16-local.conf + run: | + set -ex + echo Y | toltecctl generate-opkg-conf + opkg update + opkg install sysfs_preload + # Add steps here to test app + opkg remove sysfs_preload + path: ${{ steps.download.outputs.download-path }} diff --git a/.gitignore b/.gitignore index 7f4826b..4d4002b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ compile_commands.json *creator.user* *_qmlcache.qrc + +build/ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..456b214 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +[![rm1](https://img.shields.io/badge/rM1-supported-green)](https://remarkable.com/store/remarkable) +[![rm2](https://img.shields.io/badge/rM2-supported-green)](https://remarkable.com/store/remarkable-2) +[![opkg](https://img.shields.io/badge/OPKG-sysfs_preload-blue)](https://toltec-dev.org/) +[![Discord](https://img.shields.io/discord/385916768696139794.svg?label=reMarkable&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/ATqQGfu) + +Sysfs Preload +============= + +A simple preload that forces any calls to /sys/power/state to use systemd instead. diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0c7fdcf --- /dev/null +++ b/main.cpp @@ -0,0 +1,213 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int(*func_open)(const char*, int, mode_t) = nullptr; +static QMutex logMutex; +static std::thread stateThread; +std::atomic stateThreadRunning = false; +static int stateFds[2] = {-1, -1}; +static QMutex stateMutex; + +// Use this instead of Oxide::getAppName to avoid recursion when logging in open() +QString appName(){ + static QString name; + if(!name.isEmpty()){ + return name; + } + if(func_open == nullptr){ + return "liboxide_sysfs_preload.so"; + } + int fd = func_open("/proc/self/comm", O_RDONLY, 0); + if(fd != -1){ + FILE* f = fdopen(fd, "r"); + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + char* where = new char[size]; + rewind(f); + fread(where, sizeof(char), size, f); + name = QString::fromLatin1(where, size); + delete[] where; + } + if(!name.isEmpty()){ + return name; + } + name = QString::fromStdString(std::filesystem::canonical("/proc/self/exe").string()); + if(name.isEmpty()){ + return "liboxide_sysfs_preload.so"; + } + return name; +} + +template +void __printf(char const* file, unsigned int line, char const* func, int priority, Args... args){ + if(!qEnvironmentVariableIsSet("OXIDE_PRELOAD_DEBUG")){ + return; + } + QStringList list; + ([&]{ list << QVariant(args).toString(); }(), ...); + auto msg = list.join(' '); + sd_journal_send( + "MESSAGE=%s", msg.toStdString().c_str(), + "PRIORITY=%i", priority, + "CODE_FILE=%s", file, + "CODE_LINE=%i", line, + "CODE_FUNC=%s", func, + "SYSLOG_IDENTIFIER=%s", appName().toStdString().c_str(), + "SYSLOG_FACILITY=%s", "LOG_DAEMON", + NULL + ); + if(!isatty(fileno(stdin)) || !isatty(fileno(stdout)) || !isatty(fileno(stderr))){ + return; + } + QMutexLocker locker(&logMutex); + Q_UNUSED(locker) + std::string level; + switch(priority){ + case LOG_INFO: + level = "Info"; + break; + case LOG_WARNING: + level = "Warning"; + break; + case LOG_CRIT: + level = "Critical"; + break; + default: + level = "Debug"; + } + fprintf( + stderr, + "[%i:%i:%i %s] %s: %s (%s:%u, %s)\n", + getpgrp(), + getpid(), + gettid(), + appName().toStdString().c_str(), + level.c_str(), + msg.toStdString().c_str(), + file, + line, + func + ); +} +#define _PRINTF(priority, ...) __printf("shared/preload-sysfs/main.cpp", __LINE__, __PRETTY_FUNCTION__, priority, __VA_ARGS__) +#define _DEBUG(...) _PRINTF(LOG_DEBUG, __VA_ARGS__) +#define _WARN(...) _PRINTF(LOG_WARNING, __VA_ARGS__) +#define _INFO(...) _PRINTF(LOG_INFO, __VA_ARGS__) +#define _CRIT(...) _PRINTF(LOG_CRIT, __VA_ARGS__) + +void __thread_run(int fd){ + char line[PIPE_BUF]; + forever{ + int res = read(fd, line, PIPE_BUF); + if(res == -1){ + if(errno == EINTR){ + continue; + } + if(errno == EAGAIN || errno == EIO){ + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + _WARN("/sys/power/state pipe failed to read:", strerror(errno)); + _DEBUG("/sys/power/state pip fd:", fd); + break; + } + if(res == 0){ + continue; + } + auto data = QString::fromStdString(std::string(line, res)).trimmed(); + if((QStringList() << "mem" << "freeze" << "standby").contains(data)){ + _INFO("Suspending system due to", data, "request"); + system("systemctl suspend"); + }else{ + _WARN("Unknown power state call:", data); + } + } + stateThreadRunning = false; +} + +int __open(const char* pathname, int flags){ + if(strcmp(pathname, "/sys/power/state") != 0){ + return -2; + } + stateMutex.lock(); + if(stateFds[0] != -1){ + if(!stateThreadRunning){ + stateThread.join(); + stateThreadRunning = true; + stateThread = std::thread(__thread_run, stateFds[1]); + } + stateMutex.unlock(); + _INFO("Getting /sys/power/state pipe"); + return stateFds[0]; + } + _INFO("Opening /sys/power/state pipe"); + int socketFlags = SOCK_STREAM; + if((flags & O_NONBLOCK) || (flags & O_NDELAY)){ + socketFlags |= SOCK_NONBLOCK; + } + if(socketpair(AF_UNIX, socketFlags, 0, stateFds) == 0){ + stateThreadRunning = true; + stateThread = std::thread(__thread_run, stateFds[1]); + _INFO("/sys/power/state pipe opened"); + }else{ + _WARN("Unable to open /sys/power/state pipe:", strerror(errno)); + } + stateMutex.unlock(); + return stateFds[0]; +} + +extern "C" { + __attribute__((visibility("default"))) + int open64(const char* pathname, int flags, mode_t mode = 0){ + static const auto func_open64 = (int(*)(const char*, int, mode_t))dlsym(RTLD_NEXT, "open64"); + int fd = __open(pathname, flags); + if(fd == -2){ + fd = func_open64(pathname, flags, mode); + } + return fd; + } + + __attribute__((visibility("default"))) + int openat(int dirfd, const char* pathname, int flags, mode_t mode = 0){ + static const auto func_openat = (int(*)(int, const char*, int, mode_t))dlsym(RTLD_NEXT, "openat"); + int fd = __open(pathname, flags); + if(fd == -2){ + DIR* save = opendir("."); + fchdir(dirfd); + char path[PATH_MAX+1]; + getcwd(path, PATH_MAX); + fchdir(::dirfd(save)); + closedir(save); + fd = __open(QString("%1/%2").arg(path, pathname).toStdString().c_str(), flags); + } + if(fd == -2){ + fd = func_openat(dirfd, pathname, flags, mode); + } + return fd; + } + + __attribute__((visibility("default"))) + int open(const char* pathname, int flags, mode_t mode = 0){ + int fd = __open(pathname, flags); + if(fd == -2){ + fd = func_open(pathname, flags, mode); + } + return fd; + } + + void __attribute__ ((constructor)) init(void); + void init(void){ + func_open = (int(*)(const char*, int, mode_t))dlsym(RTLD_NEXT, "open"); + } +} diff --git a/package b/package new file mode 100644 index 0000000..51d6a8c --- /dev/null +++ b/package @@ -0,0 +1,32 @@ +pkgnames=(sysfs_preload) +pkgdesc="A simple preload that forces any calls to /sys/power/state to use systemd instead." +url=https://github.com/Eeems-Org/sysfs_preload +pkgver=1.0.0-1 +timestamp=2024-01-06T02:43Z +section=util +maintainer="Eeems " +license=MIT + +image=qt:v3.1 +source=( + main.cpp + sysfs_preload.pro + sysfs_preload.env +) +sha256sums=( + SKIP + SKIP + SKIP +) + +build() { + find . -name "*.pro" -type f -print0 \ + | xargs -r -0 sed -i 's/linux-oe-g++/linux-arm-remarkable-g++/g' + qmake + make -j$(nproc) + INSTALL_ROOT="dist" make install +} + +package() { + cp -ar "${srcdir}/dist/." "${pkgdir}" +} diff --git a/sysfs_preload.env b/sysfs_preload.env new file mode 100755 index 0000000..51ac847 --- /dev/null +++ b/sysfs_preload.env @@ -0,0 +1 @@ +export LD_PRELOAD="$LD_PRELOAD:/opt/lib/libsysfs_preload.so" diff --git a/sysfs_preload.pro b/sysfs_preload.pro new file mode 100644 index 0000000..7bd521a --- /dev/null +++ b/sysfs_preload.pro @@ -0,0 +1,25 @@ +TARGET = sysfs_preload +TEMPLATE = lib + +QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib +VERSION = 1.0 + +CONFIG += hide_symbols +CONFIG += c++17 + +QT = core +QT += network + +SOURCES += main.cpp + +LIBS += -lrt -ldl -Wl,--exclude-libs,ALL +LIBS += -lsystemd + +target.path += /opt/lib +INSTALLS += target + +xochitl_env.files = sysfs_preload.env +xochitl_env.path = /opt/etc/xochitl.env.d/ +INSTALLS += xochitl_env + +DEFINES += QT_MESSAGELOGCONTEXT