Refactored common hook code into new NickelHook library (#64)
This commit is contained in:
@@ -6,6 +6,11 @@ trigger:
|
||||
event: [push, pull_request, tag]
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker.io/geek1011/nickeltc:1.0
|
||||
when:
|
||||
event: [push, pull_request, tag]
|
||||
command: ["git", "submodule", "update", "--init", "--recursive"]
|
||||
- name: build
|
||||
image: docker.io/geek1011/nickeltc:1.0
|
||||
when:
|
||||
@@ -14,6 +19,7 @@ steps:
|
||||
- make clean
|
||||
- make all koboroot
|
||||
- mkdir out && mv KoboRoot.tgz src/libnm.so out/
|
||||
depends_on: [submodules]
|
||||
- name: build-NM_UNINSTALL_CONFIGDIR
|
||||
image: docker.io/geek1011/nickeltc:1.0
|
||||
when:
|
||||
|
||||
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -9,6 +9,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Build
|
||||
run: make all koboroot
|
||||
- name: Upload
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -9,6 +9,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Build
|
||||
run: make all koboroot
|
||||
- name: Release
|
||||
|
||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -9,6 +9,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Build
|
||||
run: cd test/syms && go build -o ../../test.syms .
|
||||
- name: Run
|
||||
|
||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -1,33 +1,20 @@
|
||||
# make gitignore
|
||||
|
||||
# KDevelop
|
||||
.kdev4/
|
||||
NickelMenu.kdev4
|
||||
*.kdev4
|
||||
.kateconfig
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# clangd
|
||||
compile_flags.txt
|
||||
|
||||
# Build artifacts
|
||||
.idea/
|
||||
.clangd/
|
||||
.cache/
|
||||
compile_commands.json
|
||||
/KoboRoot.tgz
|
||||
/src/libnm.so
|
||||
|
||||
/src/qtplugin.moc
|
||||
/src/qtplugin.o
|
||||
|
||||
/src/failsafe.o
|
||||
/src/init.o
|
||||
/src/config.o
|
||||
/src/kfmon.o
|
||||
/src/action.o
|
||||
/src/util.o
|
||||
/src/dlhook.o
|
||||
/src/generator.o
|
||||
/src/action_c.o
|
||||
/src/config.o
|
||||
/src/generator.o
|
||||
/src/generator_c.o
|
||||
|
||||
/src/menu.o
|
||||
/src/kfmon.o
|
||||
/src/util.o
|
||||
/src/action_cc.o
|
||||
/src/nickelmenu.o
|
||||
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "NickelHook"]
|
||||
path = NickelHook
|
||||
url = https://github.com/pgaskin/NickelHook.git
|
||||
branch = master
|
||||
133
Makefile
133
Makefile
@@ -1,126 +1,19 @@
|
||||
CROSS_COMPILE = arm-nickel-linux-gnueabihf-
|
||||
MOC = moc
|
||||
CC = $(CROSS_COMPILE)gcc
|
||||
CXX = $(CROSS_COMPILE)g++
|
||||
PKG_CONFIG = $(CROSS_COMPILE)pkg-config
|
||||
STRIP = $(CROSS_COMPILE)strip
|
||||
include NickelHook/NickelHook.mk
|
||||
|
||||
DESTDIR =
|
||||
override PKGCONF += Qt5Widgets
|
||||
override LIBRARY := src/libnm.so
|
||||
override SOURCES += src/action.c src/action_c.c src/action_cc.cc src/config.c src/generator.c src/generator_c.c src/kfmon.c src/nickelmenu.cc src/util.c
|
||||
override CFLAGS += -Wall -Wextra -Werror -fvisibility=hidden
|
||||
override CXXFLAGS += -Wall -Wextra -Werror -Wno-missing-field-initializers -isystemlib -fvisibility=hidden -fvisibility-inlines-hidden
|
||||
override KOBOROOT += res/doc:/mnt/onboard/.adds/nm/doc
|
||||
|
||||
ifneq ($(if $(MAKECMDGOALS),$(if $(filter-out clean gitignore install koboroot,$(MAKECMDGOALS)),YES,NO),YES),YES)
|
||||
$(info -- Skipping configure)
|
||||
else
|
||||
PTHREAD_CFLAGS := -pthread
|
||||
PTHREAD_LIBS := -pthread
|
||||
|
||||
define pkgconf =
|
||||
$(if $(filter-out undefined,$(origin $(1)_CFLAGS) $(origin $(1)_LIBS)) \
|
||||
,$(info -- Using provided CFLAGS and LIBS for $(2)) \
|
||||
,$(if $(shell $(PKG_CONFIG) --exists $(2) >/dev/null 2>/dev/null && echo y) \
|
||||
,$(info -- Found $(2) ($(shell $(PKG_CONFIG) --modversion $(2))) with pkg-config) \
|
||||
$(eval $(1)_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags $(2))) \
|
||||
$(eval $(1)_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs $(2))) \
|
||||
$(if $(3) \
|
||||
,$(if $(shell $(PKG_CONFIG) $(3) $(2) >/dev/null 2>/dev/null && echo y) \
|
||||
,$(info .. Satisfies constraint $(3)) \
|
||||
,$(info .. Does not satisfy constraint $(3)) \
|
||||
$(error Dependencies do not satisfy constraints)) \
|
||||
,) \
|
||||
,$(info -- Could not automatically detect $(2) with pkg-config. Please specify $(1)_CFLAGS and/or $(1)_LIBS manually) \
|
||||
$(error Missing dependencies)))
|
||||
endef
|
||||
|
||||
$(call pkgconf,QT5CORE,Qt5Core)
|
||||
$(call pkgconf,QT5WIDGETS,Qt5Widgets)
|
||||
|
||||
CFLAGS ?= -O2 -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb
|
||||
CXXFLAGS ?= -O2 -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb
|
||||
LDFLAGS ?= -Wl,--as-needed
|
||||
|
||||
override CFLAGS += -std=gnu11 -Wall -Wextra -Werror
|
||||
override CXXFLAGS += -std=gnu++11 -Wall -Wextra -Werror
|
||||
override LDFLAGS += -Wl,--no-undefined -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib
|
||||
|
||||
NM_VERSION := $(shell git describe --tags --always --dirty)
|
||||
# Only use it if we got something useful out of git describe...
|
||||
ifdef NM_VERSION
|
||||
override CPPFLAGS += -DNM_VERSION='"$(NM_VERSION)"'
|
||||
endif
|
||||
override SKIPCONFIGURE += strip
|
||||
strip:
|
||||
$(STRIP) --strip-unneeded src/libnm.so
|
||||
.PHONY: strip
|
||||
|
||||
ifeq ($(NM_UNINSTALL_CONFIGDIR),1)
|
||||
$(info -- NM_UNINSTALL_CONFIGDIR... enabled)
|
||||
override CPPFLAGS += -DNM_UNINSTALL_CONFIGDIR
|
||||
else
|
||||
$(info -- NM_UNINSTALL_CONFIGDIR... disabled)
|
||||
endif
|
||||
override CPPFLAGS += -DNM_UNINSTALL_CONFIGDIR
|
||||
endif
|
||||
|
||||
define GITIGNORE_HEAD
|
||||
# make gitignore
|
||||
|
||||
# KDevelop
|
||||
.kdev4/
|
||||
NickelMenu.kdev4
|
||||
.kateconfig
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# clangd
|
||||
compile_flags.txt
|
||||
|
||||
# Build artifacts
|
||||
endef
|
||||
export GITIGNORE_HEAD
|
||||
|
||||
all: src/libnm.so
|
||||
|
||||
strip: src/libnm.so
|
||||
$(STRIP) --strip-unneeded $(CURDIR)/src/libnm.so
|
||||
|
||||
clean:
|
||||
rm -f $(GENERATED)
|
||||
|
||||
gitignore:
|
||||
echo "$${GITIGNORE_HEAD}" > .gitignore
|
||||
echo '$(GENERATED)' | \
|
||||
sed 's/ /\n/g' | \
|
||||
sed 's/^./\/&/' >> .gitignore
|
||||
|
||||
install:
|
||||
install -Dm644 src/libnm.so $(DESTDIR)/usr/local/Kobo/imageformats/libnm.so
|
||||
install -Dm644 res/doc $(DESTDIR)/mnt/onboard/.adds/nm/doc
|
||||
|
||||
koboroot:
|
||||
tar cvzf KoboRoot.tgz --show-transformed --owner=root --group=root --mode="u=rwX,go=rX" --transform="s,src/libnm.so,./usr/local/Kobo/imageformats/libnm.so," --transform="s,res/doc,./mnt/onboard/.adds/nm/doc," src/libnm.so res/doc
|
||||
|
||||
.PHONY: all strip clean gitignore install koboroot
|
||||
override GENERATED += KoboRoot.tgz
|
||||
|
||||
src/libnm.so: override CFLAGS += $(PTHREAD_CFLAGS) -fvisibility=hidden -fPIC
|
||||
src/libnm.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fvisibility=hidden -fvisibility-inlines-hidden -fPIC
|
||||
src/libnm.so: override LDFLAGS += $(PTHREAD_LIBS) $(QT5CORE_LIBS) $(QT5WIDGETS_LIBS) -ldl -Wl,-soname,libnm.so
|
||||
src/libnm.so: src/qtplugin.o src/init.o src/config.o src/dlhook.o src/failsafe.o src/menu.o src/kfmon.o src/action.o src/action_c.o src/action_cc.o src/generator.o src/generator_c.o src/util.o
|
||||
|
||||
override LIBRARIES += src/libnm.so
|
||||
override MOCS += src/qtplugin.moc
|
||||
|
||||
define patw =
|
||||
$(foreach dir,src res,$(dir)/*$(1))
|
||||
endef
|
||||
define rpatw =
|
||||
$(patsubst %$(1),%$(2),$(foreach w,$(call patw,$(1)),$(wildcard $(w))))
|
||||
endef
|
||||
|
||||
$(LIBRARIES): src/%.so:
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -shared -o $@ $^ $(LDFLAGS)
|
||||
$(MOCS): %.moc: %.h
|
||||
$(MOC) $< -o $@
|
||||
$(patsubst %.moc,%.o,$(MOCS)): %.o: %.moc
|
||||
$(CXX) -xc++ $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
|
||||
$(call rpatw,.c,.o): %.o: %.c
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
||||
$(call rpatw,.cc,.o): %.o: %.cc
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
override GENERATED += $(LIBRARIES) $(MOCS) $(patsubst %.moc,%.o,$(MOCS)) $(call rpatw,.c,.o) $(call rpatw,.cc,.o)
|
||||
include NickelHook/NickelHook.mk
|
||||
|
||||
1
NickelHook
Submodule
1
NickelHook
Submodule
Submodule NickelHook added at 98931eee9c
@@ -310,7 +310,6 @@ NM_ACTION_(nickel_setting) {
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_extras) {
|
||||
|
||||
const char* mimetype;
|
||||
if (strchr(arg, '/')) mimetype = arg;
|
||||
else if (!strcmp(arg, "unblock_it")) mimetype = "application/x-games-RushHour";
|
||||
@@ -330,7 +329,6 @@ NM_ACTION_(nickel_extras) {
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_browser) {
|
||||
|
||||
bool modal;
|
||||
QUrl *url;
|
||||
QString *css;
|
||||
@@ -494,12 +492,11 @@ NM_ACTION_(power) {
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_wifi) {
|
||||
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager14sharedInstanceEv
|
||||
WirelessWorkflowManager *(*WirelessWorkflowManager_sharedInstance)();
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager14sharedInstanceEv");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_sharedInstance, "could not dlsym WirelessWorkflowManager::sharedInstance");
|
||||
|
||||
|
||||
WirelessWorkflowManager *wfm = WirelessWorkflowManager_sharedInstance();
|
||||
NM_CHECK(nullptr, wfm, "could not get shared wireless manager pointer");
|
||||
|
||||
@@ -578,7 +575,6 @@ NM_ACTION_(cmd_spawn) {
|
||||
}
|
||||
|
||||
NM_ACTION_(cmd_output) {
|
||||
|
||||
// split the timeout into timeout, put the command into cmd
|
||||
char *tmp = strdup(arg);
|
||||
char *cmd = tmp;
|
||||
|
||||
117
src/config.c
117
src/config.c
@@ -13,7 +13,6 @@
|
||||
#include "action.h"
|
||||
#include "config.h"
|
||||
#include "generator.h"
|
||||
#include "menu.h"
|
||||
#include "util.h"
|
||||
|
||||
struct nm_config_file_t {
|
||||
@@ -22,7 +21,6 @@ struct nm_config_file_t {
|
||||
nm_config_file_t *next;
|
||||
};
|
||||
|
||||
|
||||
// nm_config_files_filter skips special files, including:
|
||||
// - dotfiles
|
||||
// - vim: .*~, .*.s?? (unix only, usually swp or swo), *.swp, *.swo
|
||||
@@ -671,3 +669,118 @@ void nm_config_free(nm_config_t *cfg) {
|
||||
cfg = n;
|
||||
}
|
||||
}
|
||||
|
||||
// note: not thread safe
|
||||
static nm_config_file_t *nm_global_menu_config_files = NULL; // updated in-place by nm_global_config_update
|
||||
static nm_config_t *nm_global_menu_config = NULL; // updated by nm_global_config_update, replaced by nm_global_config_replace, NULL on error
|
||||
static nm_menu_item_t **nm_global_menu_config_items = NULL; // updated by nm_global_config_replace to an error message or the items from nm_global_menu_config
|
||||
static size_t nm_global_menu_config_n = 0; // ^
|
||||
static int nm_global_menu_config_rev = -1; // incremented by nm_global_config_update whenever the config items change for any reason
|
||||
|
||||
nm_menu_item_t **nm_global_config_items(size_t *n_out) {
|
||||
if (n_out)
|
||||
*n_out = nm_global_menu_config_n;
|
||||
return nm_global_menu_config_items;
|
||||
}
|
||||
|
||||
static void nm_global_config_replace(nm_config_t *cfg, const char *err) {
|
||||
if (nm_global_menu_config_n)
|
||||
nm_global_menu_config_n = 0;
|
||||
|
||||
if (nm_global_menu_config_items) {
|
||||
free(nm_global_menu_config_items);
|
||||
nm_global_menu_config_items = NULL;
|
||||
}
|
||||
|
||||
if (nm_global_menu_config) {
|
||||
nm_config_free(nm_global_menu_config);
|
||||
nm_global_menu_config = NULL;
|
||||
}
|
||||
|
||||
if (err && cfg)
|
||||
nm_config_free(cfg);
|
||||
|
||||
// this isn't strictly necessary, but we should always try to reparse it
|
||||
// every time just in case the error was temporary
|
||||
if (err && nm_global_menu_config_files) {
|
||||
nm_config_files_free(nm_global_menu_config_files);
|
||||
nm_global_menu_config_files = NULL;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
nm_global_menu_config_n = 1;
|
||||
nm_global_menu_config_items = calloc(nm_global_menu_config_n, sizeof(nm_menu_item_t*));
|
||||
nm_global_menu_config_items[0] = calloc(1, sizeof(nm_menu_item_t));
|
||||
nm_global_menu_config_items[0]->loc = NM_MENU_LOCATION_MAIN_MENU;
|
||||
nm_global_menu_config_items[0]->lbl = strdup("Config Error");
|
||||
nm_global_menu_config_items[0]->action = calloc(1, sizeof(nm_menu_action_t));
|
||||
nm_global_menu_config_items[0]->action->arg = strdup(err);
|
||||
nm_global_menu_config_items[0]->action->act = NM_ACTION(dbg_msg);
|
||||
nm_global_menu_config_items[0]->action->on_failure = true;
|
||||
nm_global_menu_config_items[0]->action->on_success = true;
|
||||
return;
|
||||
}
|
||||
|
||||
nm_global_menu_config = cfg;
|
||||
nm_global_menu_config_items = nm_config_get_menu(cfg, &nm_global_menu_config_n);
|
||||
if (!nm_global_menu_config_items)
|
||||
NM_LOG("could not allocate memory");
|
||||
}
|
||||
|
||||
int nm_global_config_update() {
|
||||
NM_LOG("global: scanning for config files");
|
||||
int state = nm_config_files_update(&nm_global_menu_config_files);
|
||||
if (state == -1) {
|
||||
const char *err = nm_err();
|
||||
NM_LOG("... error: %s", err);
|
||||
NM_LOG("global: freeing old config and replacing with error item");
|
||||
nm_global_config_replace(NULL, err);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_ERR_RET(nm_global_menu_config_rev, "scan for config files: %s", err);
|
||||
}
|
||||
NM_LOG("global:%s changes detected", state == 0 ? "" : " no");
|
||||
|
||||
if (state == 0) {
|
||||
NM_LOG("global: parsing new config");
|
||||
nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files);
|
||||
if (!cfg) {
|
||||
const char *err = nm_err();
|
||||
NM_LOG("... error: %s", err);
|
||||
NM_LOG("global: freeing old config and replacing with error item");
|
||||
nm_global_config_replace(NULL, err);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_ERR_RET(nm_global_menu_config_rev, "parse config files: %s", err);
|
||||
}
|
||||
|
||||
NM_LOG("global: config updated, freeing old config and replacing with new one");
|
||||
nm_global_config_replace(cfg, NULL);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_LOG("global: done swapping config");
|
||||
}
|
||||
|
||||
NM_LOG("global: running generators");
|
||||
bool g_updated = nm_config_generate(nm_global_menu_config, false);
|
||||
NM_LOG("global:%s generators updated", g_updated ? "" : " no");
|
||||
|
||||
if (g_updated) {
|
||||
NM_LOG("global: generators updated, freeing old items and replacing with new ones");
|
||||
|
||||
if (nm_global_menu_config_n)
|
||||
nm_global_menu_config_n = 0;
|
||||
|
||||
if (nm_global_menu_config_items) {
|
||||
free(nm_global_menu_config_items);
|
||||
nm_global_menu_config_items = NULL;
|
||||
}
|
||||
|
||||
nm_global_menu_config_items = nm_config_get_menu(nm_global_menu_config, &nm_global_menu_config_n);
|
||||
if (!nm_global_menu_config_items)
|
||||
NM_LOG("could not allocate memory");
|
||||
|
||||
nm_global_menu_config_rev++;
|
||||
NM_LOG("done replacing items");
|
||||
}
|
||||
|
||||
nm_err_set(NULL);
|
||||
return nm_global_menu_config_rev;
|
||||
}
|
||||
|
||||
38
src/config.h
38
src/config.h
@@ -5,7 +5,28 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "menu.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#include "action.h"
|
||||
|
||||
typedef enum {
|
||||
NM_MENU_LOCATION_MAIN_MENU = 1,
|
||||
NM_MENU_LOCATION_READER_MENU = 2,
|
||||
} nm_menu_location_t;
|
||||
|
||||
typedef struct nm_menu_action_t {
|
||||
char *arg;
|
||||
bool on_success;
|
||||
bool on_failure;
|
||||
nm_action_fn_t act; // can block, must return zero on success, nonzero with nm_err set on error
|
||||
struct nm_menu_action_t *next;
|
||||
} nm_menu_action_t;
|
||||
|
||||
typedef struct {
|
||||
nm_menu_location_t loc;
|
||||
char *lbl;
|
||||
nm_menu_action_t *action;
|
||||
} nm_menu_item_t;
|
||||
|
||||
#ifndef NM_CONFIG_DIR
|
||||
#define NM_CONFIG_DIR "/mnt/onboard/.adds/nm"
|
||||
@@ -52,6 +73,21 @@ nm_menu_item_t **nm_config_get_menu(nm_config_t *cfg, size_t *n_out);
|
||||
// nm_config_free frees all allocated memory.
|
||||
void nm_config_free(nm_config_t *cfg);
|
||||
|
||||
// nm_global_config_update updates and regenerates the config if needed. If the
|
||||
// menu items changed (i.e. the old items aren't valid anymore), the revision
|
||||
// will be incremented and returned (even if there was an error). On error,
|
||||
// nm_err is set, and otherwise, it is cleared.
|
||||
int nm_global_config_update();
|
||||
|
||||
// nm_global_config_items returns an array of pointers with the current menu
|
||||
// items (the pointer and the items it points to will remain valid until the
|
||||
// next time nm_global_config_update is called). The number of items is stored
|
||||
// in the variable pointed to by n_out. If an error ocurred during the last time
|
||||
// nm_global_config_update was called, it is returned as a "Config Error" menu
|
||||
// item. If nm_global_config_update has never been called successfully before,
|
||||
// NULL is returned and n_out is set to 0.
|
||||
nm_menu_item_t **nm_global_config_items(size_t *n_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
114
src/dlhook.c
114
src/dlhook.c
@@ -1,114 +0,0 @@
|
||||
#define _GNU_SOURCE // dladdr, dladdr1, dlinfo, Dl_info
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dlhook.h"
|
||||
#include "util.h"
|
||||
|
||||
#ifndef ELFW
|
||||
#define ELFW(type) _ElfW(ELF, __ELF_NATIVE_CLASS, type)
|
||||
#endif
|
||||
|
||||
#if __x86_64__
|
||||
#define R_JUMP_SLOT R_X86_64_JUMP_SLOT
|
||||
#else
|
||||
#if __arm__
|
||||
#define R_JUMP_SLOT R_ARM_JUMP_SLOT
|
||||
#else
|
||||
#error Please define the architecture-specific R_*_JUMP_SLOT type.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// prevent GCC from giving us warnings everywhere about the format specifiers for the ELF typedefs
|
||||
#pragma GCC diagnostic ignored "-Wformat"
|
||||
|
||||
void *nm_dlhook(void *handle, const char *symname, void *target) {
|
||||
NM_CHECK(NULL, handle && symname && target, "BUG: required arguments are null");
|
||||
|
||||
// the link_map conveniently gives use the base address without /proc/maps, and it gives us a pointer to dyn
|
||||
struct link_map *lm;
|
||||
NM_CHECK(NULL, !dlinfo(handle, RTLD_DI_LINKMAP, &lm), "could not get link_map for lib");
|
||||
NM_LOG("lib %s is mapped at %lx", lm->l_name, lm->l_addr);
|
||||
|
||||
// stuff extracted from DT_DYNAMIC
|
||||
struct {
|
||||
bool plt_is_rela;
|
||||
union {
|
||||
ElfW(Addr) _;
|
||||
ElfW(Rel) *rel;
|
||||
ElfW(Rela) *rela;
|
||||
} plt;
|
||||
ElfW(Xword) plt_sz;
|
||||
ElfW(Xword) plt_ent_sz;
|
||||
ElfW(Sym) *sym;
|
||||
const char *str;
|
||||
} dyn = {0};
|
||||
|
||||
// parse DT_DYNAMIC
|
||||
for (size_t i = 0; lm->l_ld[i].d_tag != DT_NULL; i++) {
|
||||
switch (lm->l_ld[i].d_tag) {
|
||||
case DT_PLTREL: dyn.plt_is_rela = lm->l_ld[i].d_un.d_val == DT_RELA; break; // .rel.plt - is Rel or Rela
|
||||
case DT_JMPREL: dyn.plt._ = lm->l_ld[i].d_un.d_ptr; break; // .rel.plt - offset from base
|
||||
case DT_PLTRELSZ: dyn.plt_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - size in bytes
|
||||
case DT_RELENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rel
|
||||
case DT_RELAENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rela
|
||||
case DT_SYMTAB: dyn.sym = (ElfW(Sym)*)(lm->l_ld[i].d_un.d_val); break; // .dynsym - offset
|
||||
case DT_STRTAB: dyn.str = (const char*)(lm->l_ld[i].d_un.d_val); break; // .dynstr - offset
|
||||
}
|
||||
}
|
||||
NM_LOG("DT_DYNAMIC: plt_is_rela=%d plt=%p plt_sz=%lu plt_ent_sz=%lu sym=%p str=%p", dyn.plt_is_rela, (void*)(dyn.plt)._, dyn.plt_sz, dyn.plt_ent_sz, dyn.sym, dyn.str);
|
||||
NM_CHECK(NULL, dyn.plt_ent_sz, "plt_ent_sz is zero");
|
||||
NM_CHECK(NULL, dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
|
||||
NM_CHECK(NULL, (dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel)) == dyn.plt_ent_sz, "size mismatch (%lu != %lu)", dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel), dyn.plt_ent_sz);
|
||||
|
||||
// parse the dynamic symbol table, resolve symbols to relocations, then GOT entries
|
||||
for (size_t i = 0; i < dyn.plt_sz/dyn.plt_ent_sz; i++) {
|
||||
// note: Rela is a superset of Rel
|
||||
ElfW(Rel) *rel = dyn.plt_is_rela
|
||||
? (ElfW(Rel)*)(&dyn.plt.rela[i])
|
||||
: &dyn.plt.rel[i];
|
||||
NM_CHECK(NULL, ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info));
|
||||
|
||||
ElfW(Sym) *sym = &dyn.sym[ELFW(R_SYM)(rel->r_info)];
|
||||
const char *str = &dyn.str[sym->st_name];
|
||||
if (strcmp(str, symname))
|
||||
continue;
|
||||
|
||||
void **gotoff = (void**)(lm->l_addr + rel->r_offset);
|
||||
NM_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff);
|
||||
|
||||
NM_CHECK(NULL, ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset));
|
||||
NM_CHECK(NULL, ELFW(ST_TYPE)(sym->st_info) == STT_FUNC, "not a function symbol (ST_TYPE=%d) (gotoff=%p)", ELFW(ST_TYPE)(sym->st_info), (void*)(rel->r_offset));
|
||||
NM_CHECK(NULL, ELFW(ST_BIND)(sym->st_info) == STB_GLOBAL, "not a globally bound symbol (ST_BIND=%d) (gotoff=%p)", ELFW(ST_BIND)(sym->st_info), (void*)(rel->r_offset));
|
||||
|
||||
NM_LOG("ensuring the symbol is loaded (to get the original address)");
|
||||
void *orig = dlsym(handle, symname);
|
||||
NM_CHECK(NULL, orig, "could not dlsym symbol");
|
||||
|
||||
// remove memory protection (to bypass RELRO if it is enabled)
|
||||
// note: this doesn't seem to be used on the Kobo, but we might as well stay on the safe side (plus, I test this on my local machine too)
|
||||
// note: the only way to read the current memory protection is to parse /proc/maps, but there's no harm in unprotecting it again if it's not protected
|
||||
// note: we won't put it back afterwards, as if full RELRO (i.e. RTLD_NOW) wasn't enabled, it would cause segfaults when resolving symbols later on
|
||||
NM_LOG("removing memory protection");
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
NM_CHECK(NULL, pagesize != -1, "could not get memory page size");
|
||||
void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1));
|
||||
NM_CHECK(NULL, !mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff);
|
||||
|
||||
// replace the target offset
|
||||
NM_LOG("patching symbol");
|
||||
//void *orig = *gotoff;
|
||||
*gotoff = target;
|
||||
NM_LOG("successfully patched symbol %s (orig=%p, new=%p)", str, orig, target);
|
||||
return orig;
|
||||
}
|
||||
|
||||
NM_ERR_SET("could not find symbol");
|
||||
return 0;
|
||||
}
|
||||
20
src/dlhook.h
20
src/dlhook.h
@@ -1,20 +0,0 @@
|
||||
#ifndef NM_DLHOOK_H
|
||||
#define NM_DLHOOK_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "util.h"
|
||||
|
||||
// nm_dlhook takes a lib handle from dlopen and redirects the specified symbol
|
||||
// to another, returning a pointer to the original one. Only calls from within
|
||||
// that library itself are affected (because it replaces that library's GOT).
|
||||
// This function requires glibc and Linux. It should work on any architecture,
|
||||
// and it should be resilient to most errors. If it fails, NULL is returned and
|
||||
// nm_err is set.
|
||||
NM_PRIVATE void *nm_dlhook(void *handle, const char *symname, void *target);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,78 +0,0 @@
|
||||
#define _GNU_SOURCE // asprintf, Dl_info, dladdr
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "failsafe.h"
|
||||
#include "util.h"
|
||||
|
||||
struct nm_failsafe_t {
|
||||
char *orig;
|
||||
char *tmp;
|
||||
int delay;
|
||||
};
|
||||
|
||||
nm_failsafe_t *nm_failsafe_create() {
|
||||
NM_LOG("failsafe: allocating memory");
|
||||
nm_failsafe_t *fs;
|
||||
NM_CHECK(NULL, (fs = calloc(1, sizeof(nm_failsafe_t))), "could not allocate memory");
|
||||
|
||||
NM_LOG("failsafe: finding filenames");
|
||||
Dl_info info;
|
||||
NM_CHECK(NULL, dladdr(nm_failsafe_create, &info), "could not find own path");
|
||||
NM_CHECK(NULL, info.dli_fname, "dladdr did not return a filename");
|
||||
char *d = strrchr(info.dli_fname, '.');
|
||||
NM_CHECK(NULL, !(d && !strcmp(d, ".failsafe")), "lib was loaded from the failsafe for some reason");
|
||||
NM_CHECK(NULL, (fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname);
|
||||
NM_CHECK(NULL, asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename");
|
||||
|
||||
NM_LOG("failsafe: ensuring own lib remains in memory even if it is dlclosed after being loaded with a dlopen");
|
||||
NM_CHECK(NULL, dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self");
|
||||
|
||||
NM_LOG("failsafe: renaming %s to %s", fs->orig, fs->tmp);
|
||||
NM_CHECK(NULL, !rename(fs->orig, fs->tmp), "could not rename lib");
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
static void *_nm_failsafe_destroy(void* _fs) {
|
||||
nm_failsafe_t *fs = (nm_failsafe_t*)(_fs);
|
||||
|
||||
NM_LOG("failsafe: restoring after %d seconds", fs->delay);
|
||||
sleep(fs->delay);
|
||||
|
||||
NM_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig);
|
||||
if (rename(fs->tmp, fs->orig))
|
||||
NM_LOG("error: could not rename lib");
|
||||
|
||||
NM_LOG("failsafe: freeing memory");
|
||||
free(fs->orig);
|
||||
free(fs->tmp);
|
||||
free(fs);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void nm_failsafe_destroy(nm_failsafe_t *fs, int delay) {
|
||||
fs->delay = delay;
|
||||
|
||||
NM_LOG("failsafe: scheduling restore");
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_t t;
|
||||
pthread_create(&t, &attr, _nm_failsafe_destroy, fs);
|
||||
const char name[16] = "nm_failsafe";
|
||||
pthread_setname_np(t, name);
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
|
||||
void nm_failsafe_uninstall(nm_failsafe_t *fs) {
|
||||
NM_LOG("failsafe: deleting %s", fs->tmp);
|
||||
unlink(fs->tmp);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef NM_FAILSAFE_H
|
||||
#define NM_FAILSAFE_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "util.h"
|
||||
|
||||
// nm_failsafe_t is a failsafe mechanism for injected shared libraries. It
|
||||
// works by moving it to a temporary file (so it won't get loaded the next time)
|
||||
// and dlopening itself (to prevent it from being unloaded if it is dlclose'd by
|
||||
// whatever dlopen'd it). When it is disarmed, the library is moved back to its
|
||||
// original location.
|
||||
typedef struct nm_failsafe_t nm_failsafe_t;
|
||||
|
||||
// nm_failsafe_create allocates and arms a failsafe mechanism for the currently
|
||||
// dlopen'd or LD_PRELOAD'd library. On error, NULL is returned and nm_err is
|
||||
// set.
|
||||
NM_PRIVATE nm_failsafe_t *nm_failsafe_create();
|
||||
|
||||
// nm_failsafe_destroy starts a pthread which disarms and frees the failsafe
|
||||
// after a delay. The nm_failsafe_t must not be used afterwards.
|
||||
NM_PRIVATE void nm_failsafe_destroy(nm_failsafe_t *fs, int delay);
|
||||
|
||||
// nm_failsafe_uninstall uninstalls the lib. The nm_failsafe_t must not be
|
||||
// used afterwards.
|
||||
NM_PRIVATE void nm_failsafe_uninstall(nm_failsafe_t *fs);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "action.h"
|
||||
#include "config.h"
|
||||
#include "generator.h"
|
||||
#include "menu.h"
|
||||
#include "util.h"
|
||||
|
||||
nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) {
|
||||
|
||||
@@ -6,7 +6,7 @@ extern "C" {
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
#include "menu.h"
|
||||
#include "config.h"
|
||||
|
||||
// nm_generator_fn_t generates menu items. It must return a malloc'd array of
|
||||
// pointers to malloc'd nm_menu_item_t's, and write the number of items to
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "generator.h"
|
||||
#include "menu.h"
|
||||
#include "kfmon.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
208
src/init.c
208
src/init.c
@@ -1,208 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <link.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "action.h"
|
||||
#include "config.h"
|
||||
#include "failsafe.h"
|
||||
#include "init.h"
|
||||
#include "menu.h"
|
||||
#include "util.h"
|
||||
|
||||
__attribute__((constructor)) void nm_init() {
|
||||
NM_LOG("version: " NM_VERSION);
|
||||
#ifdef NM_UNINSTALL_CONFIGDIR
|
||||
NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: true");
|
||||
#else
|
||||
NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: false");
|
||||
#endif
|
||||
NM_LOG("config dir: %s", NM_CONFIG_DIR);
|
||||
|
||||
NM_LOG("init: creating failsafe");
|
||||
|
||||
nm_failsafe_t *fs;
|
||||
if (!(fs = nm_failsafe_create())) {
|
||||
NM_LOG("error: could not create failsafe: %s, stopping", nm_err());
|
||||
goto stop;
|
||||
}
|
||||
|
||||
NM_LOG("init: checking for uninstall flag");
|
||||
|
||||
if (!access(NM_CONFIG_DIR "/uninstall", F_OK)) {
|
||||
NM_LOG("init: flag found, uninstalling");
|
||||
nm_failsafe_uninstall(fs);
|
||||
unlink(NM_CONFIG_DIR "/uninstall");
|
||||
goto stop;
|
||||
}
|
||||
|
||||
#ifdef NM_UNINSTALL_CONFIGDIR
|
||||
NM_LOG("init: NM_UNINSTALL_CONFIGDIR: checking if config dir exists");
|
||||
|
||||
if (access(NM_CONFIG_DIR, F_OK) && errno == ENOENT) {
|
||||
NM_LOG("init: config dir does not exist, uninstalling");
|
||||
nm_failsafe_uninstall(fs);
|
||||
goto stop;
|
||||
}
|
||||
#endif
|
||||
|
||||
NM_LOG("init: updating config");
|
||||
|
||||
int rev = nm_global_config_update();
|
||||
if (nm_err_peek())
|
||||
NM_LOG("init: error parsing config, will show a menu item with the error: %s", nm_err());
|
||||
|
||||
size_t ntmp = SIZE_MAX;
|
||||
if (rev == -1) {
|
||||
NM_LOG("init: no config file changes detected for initial config update (it should always return an error or update), stopping (this is a bug; err should have been returned instead)");
|
||||
return;
|
||||
} else if (!nm_global_config_items(&ntmp)) {
|
||||
NM_LOG("init: warning: no menu items returned by nm_global_config_items, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)");
|
||||
} else if (ntmp == SIZE_MAX) {
|
||||
NM_LOG("init: warning: no size returned by nm_global_config_items, ignoring for now (this is a bug)");
|
||||
} else if (!ntmp) {
|
||||
NM_LOG("init: warning: size returned by nm_global_config_items is 0, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)");
|
||||
}
|
||||
|
||||
NM_LOG("init: opening libnickel");
|
||||
|
||||
void *libnickel = dlopen("libnickel.so.1.0.0", RTLD_LAZY|RTLD_NODELETE);
|
||||
if (!libnickel) {
|
||||
NM_LOG("error: could not dlopen libnickel, stopping");
|
||||
goto stop_fs;
|
||||
}
|
||||
|
||||
NM_LOG("init: hooking libnickel");
|
||||
|
||||
if (nm_menu_hook(libnickel)) {
|
||||
NM_LOG("error: could not hook libnickel: %s, stopping", nm_err());
|
||||
goto stop_fs;
|
||||
}
|
||||
|
||||
stop_fs:
|
||||
NM_LOG("init: destroying failsafe");
|
||||
nm_failsafe_destroy(fs, 5);
|
||||
|
||||
stop:
|
||||
NM_LOG("init: done");
|
||||
return;
|
||||
}
|
||||
|
||||
// note: not thread safe
|
||||
static nm_config_file_t *nm_global_menu_config_files = NULL; // updated in-place by nm_global_config_update
|
||||
static nm_config_t *nm_global_menu_config = NULL; // updated by nm_global_config_update, replaced by nm_global_config_replace, NULL on error
|
||||
static nm_menu_item_t **nm_global_menu_config_items = NULL; // updated by nm_global_config_replace to an error message or the items from nm_global_menu_config
|
||||
static size_t nm_global_menu_config_n = 0; // ^
|
||||
static int nm_global_menu_config_rev = -1; // incremented by nm_global_config_update whenever the config items change for any reason
|
||||
|
||||
nm_menu_item_t **nm_global_config_items(size_t *n_out) {
|
||||
if (n_out)
|
||||
*n_out = nm_global_menu_config_n;
|
||||
return nm_global_menu_config_items;
|
||||
}
|
||||
|
||||
static void nm_global_config_replace(nm_config_t *cfg, const char *err) {
|
||||
if (nm_global_menu_config_n)
|
||||
nm_global_menu_config_n = 0;
|
||||
|
||||
if (nm_global_menu_config_items) {
|
||||
free(nm_global_menu_config_items);
|
||||
nm_global_menu_config_items = NULL;
|
||||
}
|
||||
|
||||
if (nm_global_menu_config) {
|
||||
nm_config_free(nm_global_menu_config);
|
||||
nm_global_menu_config = NULL;
|
||||
}
|
||||
|
||||
if (err && cfg)
|
||||
nm_config_free(cfg);
|
||||
|
||||
// this isn't strictly necessary, but we should always try to reparse it
|
||||
// every time just in case the error was temporary
|
||||
if (err && nm_global_menu_config_files) {
|
||||
nm_config_files_free(nm_global_menu_config_files);
|
||||
nm_global_menu_config_files = NULL;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
nm_global_menu_config_n = 1;
|
||||
nm_global_menu_config_items = calloc(nm_global_menu_config_n, sizeof(nm_menu_item_t*));
|
||||
nm_global_menu_config_items[0] = calloc(1, sizeof(nm_menu_item_t));
|
||||
nm_global_menu_config_items[0]->loc = NM_MENU_LOCATION_MAIN_MENU;
|
||||
nm_global_menu_config_items[0]->lbl = strdup("Config Error");
|
||||
nm_global_menu_config_items[0]->action = calloc(1, sizeof(nm_menu_action_t));
|
||||
nm_global_menu_config_items[0]->action->arg = strdup(err);
|
||||
nm_global_menu_config_items[0]->action->act = NM_ACTION(dbg_msg);
|
||||
nm_global_menu_config_items[0]->action->on_failure = true;
|
||||
nm_global_menu_config_items[0]->action->on_success = true;
|
||||
return;
|
||||
}
|
||||
|
||||
nm_global_menu_config = cfg;
|
||||
nm_global_menu_config_items = nm_config_get_menu(cfg, &nm_global_menu_config_n);
|
||||
if (!nm_global_menu_config_items)
|
||||
NM_LOG("could not allocate memory");
|
||||
}
|
||||
|
||||
int nm_global_config_update() {
|
||||
NM_LOG("global: scanning for config files");
|
||||
int state = nm_config_files_update(&nm_global_menu_config_files);
|
||||
if (state == -1) {
|
||||
const char *err = nm_err();
|
||||
NM_LOG("... error: %s", err);
|
||||
NM_LOG("global: freeing old config and replacing with error item");
|
||||
nm_global_config_replace(NULL, err);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_ERR_RET(nm_global_menu_config_rev, "scan for config files: %s", err);
|
||||
}
|
||||
NM_LOG("global:%s changes detected", state == 0 ? "" : " no");
|
||||
|
||||
if (state == 0) {
|
||||
NM_LOG("global: parsing new config");
|
||||
nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files);
|
||||
if (!cfg) {
|
||||
const char *err = nm_err();
|
||||
NM_LOG("... error: %s", err);
|
||||
NM_LOG("global: freeing old config and replacing with error item");
|
||||
nm_global_config_replace(NULL, err);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_ERR_RET(nm_global_menu_config_rev, "parse config files: %s", err);
|
||||
}
|
||||
|
||||
NM_LOG("global: config updated, freeing old config and replacing with new one");
|
||||
nm_global_config_replace(cfg, NULL);
|
||||
nm_global_menu_config_rev++;
|
||||
NM_LOG("global: done swapping config");
|
||||
}
|
||||
|
||||
NM_LOG("global: running generators");
|
||||
bool g_updated = nm_config_generate(nm_global_menu_config, false);
|
||||
NM_LOG("global:%s generators updated", g_updated ? "" : " no");
|
||||
|
||||
if (g_updated) {
|
||||
NM_LOG("global: generators updated, freeing old items and replacing with new ones");
|
||||
|
||||
if (nm_global_menu_config_n)
|
||||
nm_global_menu_config_n = 0;
|
||||
|
||||
if (nm_global_menu_config_items) {
|
||||
free(nm_global_menu_config_items);
|
||||
nm_global_menu_config_items = NULL;
|
||||
}
|
||||
|
||||
nm_global_menu_config_items = nm_config_get_menu(nm_global_menu_config, &nm_global_menu_config_n);
|
||||
if (!nm_global_menu_config_items)
|
||||
NM_LOG("could not allocate memory");
|
||||
|
||||
nm_global_menu_config_rev++;
|
||||
NM_LOG("done replacing items");
|
||||
}
|
||||
|
||||
nm_err_set(NULL);
|
||||
return nm_global_menu_config_rev;
|
||||
}
|
||||
28
src/init.h
28
src/init.h
@@ -1,28 +0,0 @@
|
||||
#ifndef NM_INIT_H
|
||||
#define NM_INIT_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "menu.h"
|
||||
|
||||
// nm_global_config_update updates and regenerates the config if needed. If the
|
||||
// menu items changed (i.e. the old items aren't valid anymore), the revision
|
||||
// will be incremented and returned (even if there was an error). On error,
|
||||
// nm_err is set, and otherwise, it is cleared.
|
||||
int nm_global_config_update();
|
||||
|
||||
// nm_global_config_items returns an array of pointers with the current menu
|
||||
// items (the pointer and the items it points to will remain valid until the
|
||||
// next time nm_global_config_update is called). The number of items is stored
|
||||
// in the variable pointed to by n_out. If an error ocurred during the last time
|
||||
// nm_global_config_update was called, it is returned as a "Config Error" menu
|
||||
// item. If nm_global_config_update has never been called successfully before,
|
||||
// NULL is returned and n_out is set to 0.
|
||||
nm_menu_item_t **nm_global_config_items(size_t *n_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
39
src/menu.h
39
src/menu.h
@@ -1,39 +0,0 @@
|
||||
#ifndef NM_MENU_H
|
||||
#define NM_MENU_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "action.h"
|
||||
|
||||
typedef enum {
|
||||
NM_MENU_LOCATION_MAIN_MENU = 1,
|
||||
NM_MENU_LOCATION_READER_MENU = 2,
|
||||
} nm_menu_location_t;
|
||||
|
||||
typedef struct nm_menu_action_t {
|
||||
char *arg;
|
||||
bool on_success;
|
||||
bool on_failure;
|
||||
nm_action_fn_t act; // can block, must return zero on success, nonzero with nm_err set on error
|
||||
struct nm_menu_action_t *next;
|
||||
} nm_menu_action_t;
|
||||
|
||||
typedef struct {
|
||||
nm_menu_location_t loc;
|
||||
char *lbl;
|
||||
nm_menu_action_t *action;
|
||||
} nm_menu_item_t;
|
||||
|
||||
// nm_menu_hook hooks a dlopen'd libnickel handle. It MUST NOT be called more
|
||||
// than once. On success, zero is returned. Otherwise, a nonzero value is
|
||||
// returned and nm_err is set.
|
||||
int nm_menu_hook(void *libnickel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -5,11 +5,10 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "dlhook.h"
|
||||
#include "init.h"
|
||||
#include "menu.h"
|
||||
#include <NickelHook.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
typedef QWidget MenuTextItem; // it's actually a subclass, but we don't need its functionality directly, so we'll stay on the safe side
|
||||
@@ -21,7 +20,6 @@ typedef void MainWindowController;
|
||||
// search menu for an example of this being used). IDK what the last parameter
|
||||
// is for, but I suspect it might be passed to QObject::setObjectName (I haven't
|
||||
// tested it directly, and it's been empty in the menus I've tried so far).
|
||||
static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem_orig)(void*, QMenu*, QString const&, bool, bool, QString const&) = NULL;
|
||||
static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem)(void*, QMenu*, QString const&, bool, bool, QString const&);
|
||||
|
||||
// AbstractNickelMenuController::createAction finishes adding the action to the
|
||||
@@ -60,42 +58,42 @@ void (*MainWindowController_toast)(MainWindowController*, QString const&, QStrin
|
||||
static void (*LightMenuSeparator_LightMenuSeparator)(void*, QWidget*);
|
||||
static void (*BoldMenuSeparator_BoldMenuSeparator)(void*, QWidget*);
|
||||
|
||||
extern "C" int nm_menu_hook(void *libnickel) {
|
||||
//libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
|
||||
//libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb");
|
||||
//libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_
|
||||
reinterpret_cast<void*&>(ConfirmationDialogFactory_showOKDialog) = dlsym(libnickel, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_");
|
||||
//libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv
|
||||
reinterpret_cast<void*&>(MainWindowController_sharedInstance) = dlsym(libnickel, "_ZN20MainWindowController14sharedInstanceEv");
|
||||
//libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i
|
||||
reinterpret_cast<void*&>(MainWindowController_toast) = dlsym(libnickel, "_ZN20MainWindowController5toastERK7QStringS2_i");
|
||||
//libnickel 4.6 * _ZN18LightMenuSeparatorC2EP7QWidget
|
||||
reinterpret_cast<void*&>(LightMenuSeparator_LightMenuSeparator) = dlsym(libnickel, "_ZN18LightMenuSeparatorC2EP7QWidget");
|
||||
//libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget
|
||||
reinterpret_cast<void*&>(BoldMenuSeparator_BoldMenuSeparator) = dlsym(libnickel, "_ZN17BoldMenuSeparatorC1EP7QWidget");
|
||||
static struct nh_info NickelMenu = (struct nh_info){
|
||||
.name = "NickelMenu",
|
||||
.desc = "Integrated launcher for Nickel.",
|
||||
.uninstall_flag = NM_CONFIG_DIR "/uninstall",
|
||||
#ifdef NM_UNINSTALL_CONFIGDIR
|
||||
.uninstall_xflag = NM_CONFIG_DIR,
|
||||
#else
|
||||
.uninstall_xflag = NULL,
|
||||
#endif
|
||||
.failsafe_delay = 2,
|
||||
};
|
||||
|
||||
NM_CHECK(1, AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)");
|
||||
NM_CHECK(1, AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)");
|
||||
NM_CHECK(1, ConfirmationDialogFactory_showOKDialog, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(String const&, QString const&)");
|
||||
NM_CHECK(1, MainWindowController_sharedInstance, "unsupported firmware: could not find MainWindowController::sharedInstance()");
|
||||
NM_CHECK(1, MainWindowController_toast, "unsupported firmware: could not find MainWindowController::toast(QString const&, QString const&, int)");
|
||||
static struct nh_hook NickelMenuHook[] = {
|
||||
{.sym = "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", .sym_new = "_nm_menu_hook", .lib = "libnickel.so.1.0.0", .out = nh_symoutptr(AbstractNickelMenuController_createMenuTextItem)}, //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
{0},
|
||||
};
|
||||
|
||||
if (!LightMenuSeparator_LightMenuSeparator)
|
||||
NM_LOG("warning: could not find LightMenuSeparator constructor, falling back to generic separators");
|
||||
if (!BoldMenuSeparator_BoldMenuSeparator)
|
||||
NM_LOG("warning: could not find BoldMenuSeparator constructor, falling back to generic separators");
|
||||
static struct nh_dlsym NickelMenuDlsym[] = {
|
||||
{.name = "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", .out = nh_symoutptr(AbstractNickelMenuController_createMenuTextItem)}, //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
{.name = "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb", .out = nh_symoutptr(AbstractNickelMenuController_createAction)}, //libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb
|
||||
{.name = "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_", .out = nh_symoutptr(ConfirmationDialogFactory_showOKDialog)}, //libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_
|
||||
{.name = "_ZN20MainWindowController14sharedInstanceEv", .out = nh_symoutptr(MainWindowController_sharedInstance)}, //libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv
|
||||
{.name = "_ZN20MainWindowController5toastERK7QStringS2_i", .out = nh_symoutptr(MainWindowController_toast)}, //libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i
|
||||
{.name = "_ZN18LightMenuSeparatorC2EP7QWidget", .out = nh_symoutptr(LightMenuSeparator_LightMenuSeparator)}, //libnickel 4.6 * _ZN18LightMenuSeparatorC2EP7QWidget
|
||||
{.name = "_ZN17BoldMenuSeparatorC1EP7QWidget", .out = nh_symoutptr(BoldMenuSeparator_BoldMenuSeparator)}, //libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget
|
||||
{0},
|
||||
};
|
||||
|
||||
void* nmh = dlsym(RTLD_DEFAULT, "_nm_menu_hook");
|
||||
NM_CHECK(1, nmh, "internal error: could not dlsym _nm_menu_hook");
|
||||
static int nm_init();
|
||||
|
||||
//libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh);
|
||||
NM_CHECK(1, AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", nm_err());
|
||||
|
||||
return 0;
|
||||
}
|
||||
NickelHook(
|
||||
.init = &nm_init,
|
||||
.info = &NickelMenu,
|
||||
.hook = NickelMenuHook,
|
||||
.dlsym = NickelMenuDlsym,
|
||||
)
|
||||
|
||||
// AbstractNickelMenuController_createAction_before wraps
|
||||
// AbstractNickelMenuController::createAction to use the correct separator for
|
||||
@@ -106,12 +104,39 @@ QAction *AbstractNickelMenuController_createAction_before(QAction *before, nm_me
|
||||
|
||||
// nm_menu_item_do runs a nm_menu_item_t and must be called from the thread of a
|
||||
// signal handler.
|
||||
void nm_menu_item_do(nm_menu_item_t *it);
|
||||
static void nm_menu_item_do(nm_menu_item_t *it);
|
||||
|
||||
// _nm_menu_inject handles the QMenu::aboutToShow signal and injects menu items.
|
||||
void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at);
|
||||
static void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at);
|
||||
|
||||
extern "C" NM_PUBLIC MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) {
|
||||
static int nm_init() {
|
||||
#ifdef NM_UNINSTALL_CONFIGDIR
|
||||
NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: true");
|
||||
#else
|
||||
NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: false");
|
||||
#endif
|
||||
|
||||
NM_LOG("updating config");
|
||||
|
||||
int rev = nm_global_config_update();
|
||||
if (nm_err_peek())
|
||||
NM_LOG("... warning: error parsing config, will show a menu item with the error: %s", nm_err());
|
||||
|
||||
size_t ntmp = SIZE_MAX;
|
||||
if (rev == -1) {
|
||||
NM_LOG("... info: no config file changes detected for initial config update (it should always return an error or update), stopping (this is a bug; err should have been returned instead)");
|
||||
} else if (!nm_global_config_items(&ntmp)) {
|
||||
NM_LOG("... warning: no menu items returned by nm_global_config_items, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)");
|
||||
} else if (ntmp == SIZE_MAX) {
|
||||
NM_LOG("... warning: no size returned by nm_global_config_items, ignoring for now (this is a bug)");
|
||||
} else if (!ntmp) {
|
||||
NM_LOG("... warning: size returned by nm_global_config_items is 0, ignoring for now (this is a bug; it should always have a menu item whether the default, an error, or the actual config)");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) {
|
||||
NM_LOG("AbstractNickelMenuController::createMenuTextItem(%p, `%s`, %d, %d, `%s`)", menu, qPrintable(label), checkable, checked, qPrintable(thingy));
|
||||
|
||||
QString trmm = QCoreApplication::translate("StatusBarMenuController", "Settings");
|
||||
@@ -130,7 +155,7 @@ extern "C" NM_PUBLIC MenuTextItem* _nm_menu_hook(void* _this, QMenu* menu, QStri
|
||||
if (loc)
|
||||
QObject::connect(menu, &QMenu::aboutToShow, std::bind(_nm_menu_inject, _this, menu, loc, menu->actions().count()));
|
||||
|
||||
return AbstractNickelMenuController_createMenuTextItem_orig(_this, menu, label, checkable, checked, thingy);
|
||||
return AbstractNickelMenuController_createMenuTextItem(_this, menu, label, checkable, checked, thingy);
|
||||
}
|
||||
|
||||
void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) {
|
||||
@@ -185,7 +210,7 @@ void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) {
|
||||
|
||||
NM_LOG("adding item '%s'...", it->lbl);
|
||||
|
||||
MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem_orig(nmc, menu, QString::fromUtf8(it->lbl), false, false, "");
|
||||
MenuTextItem* item = AbstractNickelMenuController_createMenuTextItem(nmc, menu, QString::fromUtf8(it->lbl), false, false, "");
|
||||
QAction* action = AbstractNickelMenuController_createAction_before(before, loc, i == items_n-1, nmc, menu, item, true, true, true);
|
||||
|
||||
QObject::connect(action, &QAction::triggered, [it](bool){
|
||||
@@ -306,3 +331,4 @@ QAction *AbstractNickelMenuController_createAction_before(QAction *before, nm_me
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifdef __cplusplus
|
||||
#include <QImageIOHandler>
|
||||
#include <QObject>
|
||||
#include <QtPlugin>
|
||||
|
||||
// we make it a fake image plugin so we can have Qt automatically load it
|
||||
// without needing extra configuration (e.g. LD_PRELOAD, -plugin arg, etc).
|
||||
|
||||
class NMPlugin : public QImageIOPlugin {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface")
|
||||
public:
|
||||
Capabilities capabilities(QIODevice*, QByteArray const&) const { return 0; };
|
||||
QImageIOHandler *create(QIODevice*, QByteArray const& = QByteArray()) const { return 0; };
|
||||
};
|
||||
|
||||
#endif
|
||||
21
src/util.h
21
src/util.h
@@ -9,18 +9,7 @@ extern "C" {
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
|
||||
// Fallback version tag
|
||||
#ifndef NM_VERSION
|
||||
#define NM_VERSION "dev"
|
||||
#endif
|
||||
|
||||
#ifndef NM_LOG_NAME
|
||||
#define NM_LOG_NAME "NickelMenu"
|
||||
#endif
|
||||
|
||||
// Symbol visibility (to prevent conflicts when reusing parts of NM)
|
||||
#define NM_PUBLIC __attribute__((visibility("default")))
|
||||
#define NM_PRIVATE __attribute__((visibility("hidden")))
|
||||
#include <NickelHook.h>
|
||||
|
||||
// strtrim trims ASCII whitespace in-place (i.e. don't give it a string literal)
|
||||
// from the left/right of the string.
|
||||
@@ -34,22 +23,22 @@ inline char *strtrim(char *s) {
|
||||
}
|
||||
|
||||
// NM_LOG writes a log message.
|
||||
#define NM_LOG(fmt, ...) syslog(LOG_DEBUG, ("(" NM_LOG_NAME ") " fmt " (%s:%d)"), ##__VA_ARGS__, __FILE__, __LINE__)
|
||||
#define NM_LOG(fmt, ...) nh_log(fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__)
|
||||
|
||||
// Error handling (thread-safe):
|
||||
|
||||
// nm_err returns the current error message and clears the error state. If there
|
||||
// isn't any error set, NULL is returned. The returned string is only valid on
|
||||
// the current thread until nm_err_set is called.
|
||||
NM_PRIVATE const char *nm_err();
|
||||
const char *nm_err();
|
||||
|
||||
// nm_err_peek is like nm_err, but doesn't clear the error state.
|
||||
NM_PRIVATE const char *nm_err_peek();
|
||||
const char *nm_err_peek();
|
||||
|
||||
// nm_err_set sets the current error message to the specified format string. If
|
||||
// fmt is NULL, the error is cleared. It is safe to use the return value of
|
||||
// nm_err as an argument. If fmt was not NULL, true is returned.
|
||||
NM_PRIVATE bool nm_err_set(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
bool nm_err_set(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
|
||||
// NM_ERR_SET set is like nm_err_set, but also includes information about the
|
||||
// current file/line. To set it to NULL, use nm_err_set directly.
|
||||
|
||||
Reference in New Issue
Block a user