Refactored error handling (#62)
Instead of using and checking malloc'd error strings formatted with asprintf and passed through a pointer in function arguments, use a global thread-local statically allocated error buffer with functions to set and get it. This is more efficient, less error-prone, and easier to use than the old method, especially now that NM is larger (meaning that errors are returned through multiple layers) and parts of it are re-used in other things.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ compile_flags.txt
|
||||
/src/config.o
|
||||
/src/kfmon.o
|
||||
/src/action.o
|
||||
/src/util.o
|
||||
/src/dlhook.o
|
||||
/src/generator.o
|
||||
/src/action_c.o
|
||||
|
||||
2
Makefile
2
Makefile
@@ -100,7 +100,7 @@ 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/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
|
||||
|
||||
@@ -30,7 +30,6 @@ _nm_action_result_fmt(toast, NM_ACTION_RESULT_TYPE_TOAST);
|
||||
void nm_action_result_free(nm_action_result_t *res) {
|
||||
if (!res)
|
||||
return;
|
||||
|
||||
if (res->msg)
|
||||
free(res->msg);
|
||||
free(res);
|
||||
|
||||
@@ -17,7 +17,10 @@ typedef struct {
|
||||
int skip; // for use by skip only
|
||||
} nm_action_result_t;
|
||||
|
||||
typedef nm_action_result_t *(*nm_action_fn_t)(const char *arg, char **err);
|
||||
// nm_action_fn_t represents an action. On success, a nm_action_result_t is
|
||||
// returned and needs to be freed with nm_action_result_free. Otherwise, NULL is
|
||||
// returned and nm_err is set.
|
||||
typedef nm_action_result_t *(*nm_action_fn_t)(const char *arg);
|
||||
|
||||
nm_action_result_t *nm_action_result_silent();
|
||||
nm_action_result_t *nm_action_result_msg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
@@ -27,9 +30,9 @@ void nm_action_result_free(nm_action_result_t *res);
|
||||
#define NM_ACTION(name) nm_action_##name
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define NM_ACTION_(name) extern "C" nm_action_result_t *NM_ACTION(name)(const char *arg, char **err_out)
|
||||
#define NM_ACTION_(name) extern "C" nm_action_result_t *NM_ACTION(name)(const char *arg)
|
||||
#else
|
||||
#define NM_ACTION_(name) nm_action_result_t *NM_ACTION(name)(const char *arg, char **err_out)
|
||||
#define NM_ACTION_(name) nm_action_result_t *NM_ACTION(name)(const char *arg)
|
||||
#endif
|
||||
|
||||
#define NM_ACTIONS \
|
||||
|
||||
@@ -6,49 +6,38 @@
|
||||
#include "util.h"
|
||||
|
||||
NM_ACTION_(dbg_syslog) {
|
||||
#define NM_ERR_RET NULL
|
||||
NM_LOG("dbgsyslog: %s", arg);
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(dbg_error) {
|
||||
#define NM_ERR_RET NULL
|
||||
NM_RETURN_ERR("%s", arg);
|
||||
#undef NM_ERR_RET
|
||||
NM_ERR_SET("%s", arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NM_ACTION_(dbg_msg) {
|
||||
#define NM_ERR_RET NULL
|
||||
NM_RETURN_OK(nm_action_result_msg("%s", arg));
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_msg("%s", arg);
|
||||
}
|
||||
|
||||
NM_ACTION_(dbg_toast) {
|
||||
#define NM_ERR_RET NULL
|
||||
NM_RETURN_OK(nm_action_result_toast("%s", arg));
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_toast("%s", arg);
|
||||
}
|
||||
|
||||
NM_ACTION_(skip) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
char *tmp;
|
||||
long n = strtol(arg, &tmp, 10);
|
||||
NM_ASSERT(*arg && !*tmp && n != 0 && n >= -1 && n < INT_MAX, "invalid count '%s': must be a nonzero integer or -1", arg);
|
||||
NM_CHECK(NULL, *arg && !*tmp && n != 0 && n >= -1 && n < INT_MAX, "invalid count '%s': must be a nonzero integer or -1", arg);
|
||||
|
||||
nm_action_result_t *res = calloc(1, sizeof(nm_action_result_t));
|
||||
res->type = NM_ACTION_RESULT_TYPE_SKIP;
|
||||
res->skip = (int)(n);
|
||||
NM_RETURN_OK(res);
|
||||
|
||||
#undef NM_ERR_RET
|
||||
return res;
|
||||
}
|
||||
|
||||
NM_ACTION_(kfmon_id) {
|
||||
// Start by watch ID (simpler, but IDs may not be stable across a single power cycle, given severe KFMon config shuffling)
|
||||
int status = nm_kfmon_simple_request("start", arg);
|
||||
return nm_kfmon_return_handler(status, err_out);
|
||||
return nm_kfmon_return_handler(status);
|
||||
}
|
||||
|
||||
NM_ACTION_(kfmon) {
|
||||
@@ -56,9 +45,8 @@ NM_ACTION_(kfmon) {
|
||||
int status = nm_kfmon_simple_request("trigger", arg);
|
||||
|
||||
// Fixup INVALID_ID to INVALID_NAME for slightly clearer feedback (see e8b2588 for details).
|
||||
if (status == KFMON_IPC_ERR_INVALID_ID) {
|
||||
if (status == KFMON_IPC_ERR_INVALID_ID)
|
||||
status = KFMON_IPC_ERR_INVALID_NAME;
|
||||
}
|
||||
|
||||
return nm_kfmon_return_handler(status, err_out);
|
||||
return nm_kfmon_return_handler(status);
|
||||
}
|
||||
|
||||
185
src/action_cc.cc
185
src/action_cc.cc
@@ -53,15 +53,14 @@ typedef void N3PowerWorkflowManager;
|
||||
typedef void WirelessWorkflowManager;
|
||||
|
||||
NM_ACTION_(nickel_open) {
|
||||
#define NM_ERR_RET nullptr
|
||||
char *tmp1 = strdupa(arg); // strsep and strtrim will modify it
|
||||
char *arg1 = strtrim(strsep(&tmp1, ":"));
|
||||
char *arg2 = strtrim(tmp1);
|
||||
NM_ASSERT(arg2, "could not find a : in the argument");
|
||||
NM_CHECK(nullptr, arg2, "could not find a : in the argument");
|
||||
|
||||
const char *sym_c = NULL; // *NavMixin constructor (subclass of QObject)
|
||||
const char *sym_d = NULL; // *NavMixin destructor (D1, not D0 because it also tries to call delete)
|
||||
const char *sym_f = NULL; // *NavMixin::* function
|
||||
const char *sym_c = nullptr; // *NavMixin constructor (subclass of QObject)
|
||||
const char *sym_d = nullptr; // *NavMixin destructor (D1, not D0 because it also tries to call delete)
|
||||
const char *sym_f = nullptr; // *NavMixin::* function
|
||||
|
||||
if (!strcmp(arg1, "discover")) {
|
||||
sym_c = "_ZN16DiscoverNavMixinC1Ev"; //libnickel 4.6 * _ZN16DiscoverNavMixinC1Ev
|
||||
@@ -97,9 +96,9 @@ NM_ACTION_(nickel_open) {
|
||||
else if (!strcmp(arg2, "search")) sym_f = "_ZN13StoreNavMixin6searchEv"; //libnickel 4.6 * _ZN13StoreNavMixin6searchEv
|
||||
}
|
||||
|
||||
NM_ASSERT(sym_c, "unknown category '%s' (in '%s:%s')", arg1, arg1, arg2);
|
||||
NM_ASSERT(sym_d, "destructor not specified (this is a bug)");
|
||||
NM_ASSERT(sym_f, "unknown view '%s' (in '%s:%s')", arg2, arg1, arg2);
|
||||
NM_CHECK(nullptr, sym_c, "unknown category '%s' (in '%s:%s')", arg1, arg1, arg2);
|
||||
NM_CHECK(nullptr, sym_d, "destructor not specified (this is a bug)");
|
||||
NM_CHECK(nullptr, sym_f, "unknown view '%s' (in '%s:%s')", arg2, arg1, arg2);
|
||||
|
||||
void (*fn_c)(QObject *_this);
|
||||
void (*fn_d)(QObject *_this);
|
||||
@@ -109,9 +108,9 @@ NM_ACTION_(nickel_open) {
|
||||
reinterpret_cast<void*&>(fn_d) = dlsym(RTLD_DEFAULT, sym_d);
|
||||
reinterpret_cast<void*&>(fn_f) = dlsym(RTLD_DEFAULT, sym_f);
|
||||
|
||||
NM_ASSERT(fn_c, "could not find constructor %s (is your firmware too old?)", sym_c);
|
||||
NM_ASSERT(fn_d, "could not find destructor %s (is your firmware too old?)", sym_d);
|
||||
NM_ASSERT(fn_f, "could not find function %s (is your firmware too old?)", sym_f);
|
||||
NM_CHECK(nullptr, fn_c, "could not find constructor %s (is your firmware too old?)", sym_c);
|
||||
NM_CHECK(nullptr, fn_d, "could not find destructor %s (is your firmware too old?)", sym_d);
|
||||
NM_CHECK(nullptr, fn_f, "could not find function %s (is your firmware too old?)", sym_f);
|
||||
NM_LOG("c: %s = %p; d: %s = %p; f: %s = %p", sym_c, fn_c, sym_d, fn_d, sym_f, fn_f);
|
||||
|
||||
QObject obj(nullptr);
|
||||
@@ -119,13 +118,10 @@ NM_ACTION_(nickel_open) {
|
||||
fn_f(&obj);
|
||||
fn_d(&obj);
|
||||
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_setting) {
|
||||
#define NM_ERR_RET nullptr
|
||||
|
||||
enum {
|
||||
mode_toggle,
|
||||
mode_enable,
|
||||
@@ -135,7 +131,7 @@ NM_ACTION_(nickel_setting) {
|
||||
char *tmp1 = strdupa(arg); // strsep and strtrim will modify it
|
||||
char *arg1 = strtrim(strsep(&tmp1, ":"));
|
||||
char *arg2 = strtrim(tmp1);
|
||||
NM_ASSERT(arg2, "could not find a : in the argument");
|
||||
NM_CHECK(nullptr, arg2, "could not find a : in the argument");
|
||||
|
||||
if (!strcmp(arg1, "toggle"))
|
||||
mode = mode_toggle;
|
||||
@@ -144,39 +140,39 @@ NM_ACTION_(nickel_setting) {
|
||||
else if (!strcmp(arg1, "disable"))
|
||||
mode = mode_disable;
|
||||
else
|
||||
NM_RETURN_ERR("unknown action '%s' for nickel_setting: expected 'toggle', 'enable', or 'disable'", arg1);
|
||||
NM_ERR_RET(nullptr, "unknown action '%s' for nickel_setting: expected 'toggle', 'enable', or 'disable'", arg1);
|
||||
|
||||
//libnickel 4.6 * _ZN6Device16getCurrentDeviceEv
|
||||
Device *(*Device_getCurrentDevice)();
|
||||
reinterpret_cast<void*&>(Device_getCurrentDevice) = dlsym(RTLD_DEFAULT, "_ZN6Device16getCurrentDeviceEv");
|
||||
NM_ASSERT(Device_getCurrentDevice, "could not dlsym Device::getCurrentDevice");
|
||||
NM_CHECK(nullptr, Device_getCurrentDevice, "could not dlsym Device::getCurrentDevice");
|
||||
|
||||
//libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device
|
||||
void *(*Settings_Settings)(Settings*, Device*, bool);
|
||||
void *(*Settings_SettingsLegacy)(Settings*, Device*);
|
||||
reinterpret_cast<void*&>(Settings_Settings) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Deviceb");
|
||||
reinterpret_cast<void*&>(Settings_SettingsLegacy) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Device");
|
||||
NM_ASSERT(Settings_Settings || Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)");
|
||||
NM_CHECK(nullptr, Settings_Settings || Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)");
|
||||
|
||||
//libnickel 4.6 * _ZN8SettingsD2Ev
|
||||
void *(*Settings_SettingsD)(Settings*);
|
||||
reinterpret_cast<void*&>(Settings_SettingsD) = dlsym(RTLD_DEFAULT, "_ZN8SettingsD2Ev");
|
||||
NM_ASSERT(Settings_SettingsD, "could not dlsym Settings destructor");
|
||||
NM_CHECK(nullptr, Settings_SettingsD, "could not dlsym Settings destructor");
|
||||
|
||||
// some settings don't have symbols in a usable form, and some are inlined, so we may need to set them directly
|
||||
//libnickel 4.6 * _ZN8Settings10getSettingERK7QStringRK8QVariant
|
||||
QVariant (*Settings_getSetting)(Settings*, QString const&, QVariant const&); // the last param is the default, also note that this requires a subclass of Settings
|
||||
reinterpret_cast<void*&>(Settings_getSetting) = dlsym(RTLD_DEFAULT, "_ZN8Settings10getSettingERK7QStringRK8QVariant");
|
||||
NM_ASSERT(Settings_getSetting, "could not dlsym Settings::getSetting");
|
||||
NM_CHECK(nullptr, Settings_getSetting, "could not dlsym Settings::getSetting");
|
||||
|
||||
// ditto
|
||||
//libnickel 4.6 * _ZN8Settings11saveSettingERK7QStringRK8QVariantb
|
||||
void *(*Settings_saveSetting)(Settings*, QString const&, QVariant const&, bool); // the last param is whether to do a full disk sync immediately (rather than waiting for the kernel to do it)
|
||||
reinterpret_cast<void*&>(Settings_saveSetting) = dlsym(RTLD_DEFAULT, "_ZN8Settings11saveSettingERK7QStringRK8QVariantb");
|
||||
NM_ASSERT(Settings_saveSetting, "could not dlsym Settings::saveSetting");
|
||||
NM_CHECK(nullptr, Settings_saveSetting, "could not dlsym Settings::saveSetting");
|
||||
|
||||
Device *dev = Device_getCurrentDevice();
|
||||
NM_ASSERT(dev, "could not get shared nickel device pointer");
|
||||
NM_CHECK(nullptr, dev, "could not get shared nickel device pointer");
|
||||
|
||||
Settings *settings = alloca(128); // way larger than it is, but better to be safe
|
||||
if (Settings_Settings)
|
||||
@@ -205,27 +201,27 @@ NM_ACTION_(nickel_setting) {
|
||||
|
||||
//libnickel 4.6 * _ZTV8Settings
|
||||
void *Settings_vtable = dlsym(RTLD_DEFAULT, "_ZTV8Settings");
|
||||
NM_ASSERT(Settings_vtable, "could not dlsym the vtable for Settings");
|
||||
NM_ASSERT(vtable_ptr(settings) == vtable_target(Settings_vtable), "unexpected vtable layout (expected class to start with a pointer to 8 bytes into the vtable)");
|
||||
NM_CHECK(nullptr, Settings_vtable, "could not dlsym the vtable for Settings");
|
||||
NM_CHECK(nullptr, vtable_ptr(settings) == vtable_target(Settings_vtable), "unexpected vtable layout (expected class to start with a pointer to 8 bytes into the vtable)");
|
||||
|
||||
bool v = mode == mode_disable; // this gets inverted
|
||||
|
||||
if (!strcmp(arg2, "invert") || !strcmp(arg2, "screenshots")) {
|
||||
//libnickel 4.6 * _ZTV15FeatureSettings
|
||||
void *FeatureSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV15FeatureSettings");
|
||||
NM_ASSERT(FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings");
|
||||
NM_CHECK(nullptr, FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings");
|
||||
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
|
||||
|
||||
if (!strcmp(arg2, "invert")) {
|
||||
//libnickel 4.6 * _ZN15FeatureSettings12invertScreenEv
|
||||
bool (*FeatureSettings_invertScreen)(Settings*);
|
||||
reinterpret_cast<void*&>(FeatureSettings_invertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings12invertScreenEv");
|
||||
NM_ASSERT(FeatureSettings_invertScreen, "could not dlsym FeatureSettings::invertScreen");
|
||||
NM_CHECK(nullptr, FeatureSettings_invertScreen, "could not dlsym FeatureSettings::invertScreen");
|
||||
|
||||
//libnickel 4.6 * _ZN15FeatureSettings15setInvertScreenEb
|
||||
bool (*FeatureSettings_setInvertScreen)(Settings*, bool);
|
||||
reinterpret_cast<void*&>(FeatureSettings_setInvertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings15setInvertScreenEb");
|
||||
NM_ASSERT(FeatureSettings_setInvertScreen, "could not dlsym FeatureSettings::setInvertScreen");
|
||||
NM_CHECK(nullptr, FeatureSettings_setInvertScreen, "could not dlsym FeatureSettings::setInvertScreen");
|
||||
|
||||
if (mode == mode_toggle) {
|
||||
v = FeatureSettings_invertScreen(settings);
|
||||
@@ -235,7 +231,7 @@ NM_ACTION_(nickel_setting) {
|
||||
FeatureSettings_setInvertScreen(settings, !v);
|
||||
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
|
||||
|
||||
NM_ASSERT(FeatureSettings_invertScreen(settings) == !v, "failed to set setting");
|
||||
NM_CHECK(nullptr, FeatureSettings_invertScreen(settings) == !v, "failed to set setting");
|
||||
vtable_ptr(settings) = vtable_target(FeatureSettings_vtable);
|
||||
|
||||
QWidget *w = QApplication::topLevelAt(25, 25);
|
||||
@@ -257,18 +253,18 @@ NM_ACTION_(nickel_setting) {
|
||||
}
|
||||
} else if (!strcmp(arg2, "lockscreen")) {
|
||||
void *PowerSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV13PowerSettings");
|
||||
NM_ASSERT(PowerSettings_vtable, "could not dlsym the vtable for PowerSettings");
|
||||
NM_CHECK(nullptr, PowerSettings_vtable, "could not dlsym the vtable for PowerSettings");
|
||||
vtable_ptr(settings) = vtable_target(PowerSettings_vtable);
|
||||
|
||||
//libnickel 4.12.12111 * _ZN13PowerSettings16getUnlockEnabledEv
|
||||
bool (*PowerSettings__getUnlockEnabled)(Settings*);
|
||||
reinterpret_cast<void*&>(PowerSettings__getUnlockEnabled) = dlsym(RTLD_DEFAULT, "_ZN13PowerSettings16getUnlockEnabledEv");
|
||||
NM_ASSERT(PowerSettings__getUnlockEnabled, "could not dlsym PowerSettings::getUnlockEnabled");
|
||||
NM_CHECK(nullptr, PowerSettings__getUnlockEnabled, "could not dlsym PowerSettings::getUnlockEnabled");
|
||||
|
||||
//libnickel 4.12.12111 * _ZN13PowerSettings16setUnlockEnabledEb
|
||||
bool (*PowerSettings__setUnlockEnabled)(Settings*, bool);
|
||||
reinterpret_cast<void*&>(PowerSettings__setUnlockEnabled) = dlsym(RTLD_DEFAULT, "_ZN13PowerSettings16setUnlockEnabledEb");
|
||||
NM_ASSERT(PowerSettings__setUnlockEnabled, "could not dlsym PowerSettings::setUnlockEnabled");
|
||||
NM_CHECK(nullptr, PowerSettings__setUnlockEnabled, "could not dlsym PowerSettings::setUnlockEnabled");
|
||||
|
||||
if (mode == mode_toggle) {
|
||||
v = PowerSettings__getUnlockEnabled(settings);
|
||||
@@ -278,12 +274,12 @@ NM_ACTION_(nickel_setting) {
|
||||
PowerSettings__setUnlockEnabled(settings, !v);
|
||||
vtable_ptr(settings) = vtable_target(PowerSettings_vtable);
|
||||
|
||||
NM_ASSERT(PowerSettings__getUnlockEnabled(settings) == !v, "failed to set setting");
|
||||
NM_CHECK(nullptr, PowerSettings__getUnlockEnabled(settings) == !v, "failed to set setting");
|
||||
vtable_ptr(settings) = vtable_target(PowerSettings_vtable);
|
||||
} else if (!strcmp(arg2, "force_wifi")) {
|
||||
//libnickel 4.6 * _ZTV11DevSettings
|
||||
void *PowerSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV11DevSettings");
|
||||
NM_ASSERT(PowerSettings_vtable, "could not dlsym the vtable for DevSettings");
|
||||
NM_CHECK(nullptr, PowerSettings_vtable, "could not dlsym the vtable for DevSettings");
|
||||
vtable_ptr(settings) = vtable_target(PowerSettings_vtable);
|
||||
|
||||
if (mode == mode_toggle) {
|
||||
@@ -300,7 +296,7 @@ NM_ACTION_(nickel_setting) {
|
||||
} else {
|
||||
// TODO: more settings?
|
||||
Settings_SettingsD(settings);
|
||||
NM_RETURN_ERR("unknown setting name '%s' (arg: '%s')", arg1, arg);
|
||||
NM_ERR_RET(nullptr, "unknown setting name '%s' (arg: '%s')", arg1, arg);
|
||||
}
|
||||
|
||||
#undef vtable_ptr
|
||||
@@ -308,14 +304,12 @@ NM_ACTION_(nickel_setting) {
|
||||
|
||||
Settings_SettingsD(settings);
|
||||
|
||||
NM_RETURN_OK(strcmp(arg2, "invert") // invert is obvious
|
||||
return strcmp(arg2, "invert") // invert is obvious
|
||||
? nm_action_result_toast("%s %s", v ? "disabled" : "enabled", arg2)
|
||||
: nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
: nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_extras) {
|
||||
#define NM_ERR_RET nullptr
|
||||
|
||||
const char* mimetype;
|
||||
if (strchr(arg, '/')) mimetype = arg;
|
||||
@@ -324,20 +318,18 @@ NM_ACTION_(nickel_extras) {
|
||||
else if (!strcmp(arg, "solitaire")) mimetype = "application/x-games-Solitaire";
|
||||
else if (!strcmp(arg, "sudoku")) mimetype = "application/x-games-Sudoku";
|
||||
else if (!strcmp(arg, "word_scramble")) mimetype = "application/x-games-Boggle";
|
||||
else NM_RETURN_ERR("unknown beta feature name or plugin mimetype '%s'", arg);
|
||||
else NM_ERR_RET(nullptr, "unknown beta feature name or plugin mimetype '%s'", arg);
|
||||
|
||||
//libnickel 4.6 * _ZN18ExtrasPluginLoader10loadPluginEPKc
|
||||
void (*ExtrasPluginLoader_loadPlugin)(const char*);
|
||||
reinterpret_cast<void*&>(ExtrasPluginLoader_loadPlugin) = dlsym(RTLD_DEFAULT, "_ZN18ExtrasPluginLoader10loadPluginEPKc");
|
||||
NM_ASSERT(ExtrasPluginLoader_loadPlugin, "could not dlsym ExtrasPluginLoader::loadPlugin");
|
||||
NM_CHECK(nullptr, ExtrasPluginLoader_loadPlugin, "could not dlsym ExtrasPluginLoader::loadPlugin");
|
||||
ExtrasPluginLoader_loadPlugin(mimetype);
|
||||
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_browser) {
|
||||
#define NM_ERR_RET nullptr
|
||||
|
||||
bool modal;
|
||||
QUrl *url;
|
||||
@@ -361,12 +353,12 @@ NM_ACTION_(nickel_browser) {
|
||||
css = new QString(tmp.section(' ', 1).trimmed());
|
||||
url = new QUrl(tmp.section(' ', 0, 0).trimmed(), QUrl::ParsingMode::StrictMode);
|
||||
if (!url->isValid() || url->isRelative())
|
||||
NM_RETURN_ERR("invalid url '%s' (argument: '%s') (note: if your url has spaces, they need to be escaped)", qPrintable(tmp.section(' ', 0, 0)), arg);
|
||||
NM_ERR_RET(nullptr, "invalid url '%s' (argument: '%s') (note: if your url has spaces, they need to be escaped)", qPrintable(tmp.section(' ', 0, 0)), arg);
|
||||
} else if (tmp.length()) {
|
||||
url = new QUrl(tmp, QUrl::ParsingMode::StrictMode);
|
||||
css = new QString("");
|
||||
if (!url->isValid() || url->isRelative())
|
||||
NM_RETURN_ERR("invalid url '%s' (argument: '%s') (note: if your url has spaces, they need to be escaped)", qPrintable(tmp.section(' ', 0, 0)), arg);
|
||||
NM_ERR_RET(nullptr, "invalid url '%s' (argument: '%s') (note: if your url has spaces, they need to be escaped)", qPrintable(tmp.section(' ', 0, 0)), arg);
|
||||
} else {
|
||||
url = new QUrl();
|
||||
css = new QString("");
|
||||
@@ -380,12 +372,12 @@ NM_ACTION_(nickel_browser) {
|
||||
void (*BrowserWorkflowManager_BrowserWorkflowManager)(BrowserWorkflowManager*, QObject*); // 4.11.11911+
|
||||
reinterpret_cast<void*&>(BrowserWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManager14sharedInstanceEv");
|
||||
reinterpret_cast<void*&>(BrowserWorkflowManager_BrowserWorkflowManager) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManagerC1EP7QObject");
|
||||
NM_ASSERT(BrowserWorkflowManager_sharedInstance || BrowserWorkflowManager_BrowserWorkflowManager, "could not dlsym BrowserWorkflowManager constructor (4.11.11911+) or sharedInstance");
|
||||
NM_CHECK(nullptr, BrowserWorkflowManager_sharedInstance || BrowserWorkflowManager_BrowserWorkflowManager, "could not dlsym BrowserWorkflowManager constructor (4.11.11911+) or sharedInstance");
|
||||
|
||||
//libnickel 4.6 * _ZN22BrowserWorkflowManager11openBrowserEbRK4QUrlRK7QString
|
||||
void (*BrowserWorkflowManager_openBrowser)(BrowserWorkflowManager*, bool, QUrl*, QString*); // the bool is whether to open it as a modal, the QUrl is the URL to load(if !QUrl::isValid(), it loads the homepage), the string is CSS to inject
|
||||
reinterpret_cast<void*&>(BrowserWorkflowManager_openBrowser) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManager11openBrowserEbRK4QUrlRK7QString");
|
||||
NM_ASSERT(BrowserWorkflowManager_openBrowser, "could not dlsym BrowserWorkflowManager::openBrowser");
|
||||
NM_CHECK(nullptr, BrowserWorkflowManager_openBrowser, "could not dlsym BrowserWorkflowManager::openBrowser");
|
||||
|
||||
// note: everything must be on the heap since if it isn't connected, it
|
||||
// passes it as-is to the connected signal, which will be used
|
||||
@@ -395,62 +387,60 @@ NM_ACTION_(nickel_browser) {
|
||||
|
||||
if (BrowserWorkflowManager_BrowserWorkflowManager) {
|
||||
bwm = calloc(1, 128); // as of 4.20.14622, it's actually 20 bytes, but we're going to stay on the safe side
|
||||
NM_ASSERT(bwm, "could not allocate memory for BrowserWorkflowManager");
|
||||
NM_CHECK(nullptr, bwm, "could not allocate memory for BrowserWorkflowManager");
|
||||
BrowserWorkflowManager_BrowserWorkflowManager(bwm, nullptr);
|
||||
} else {
|
||||
bwm = BrowserWorkflowManager_sharedInstance();
|
||||
NM_ASSERT(bwm, "could not get shared browser workflow manager pointer");
|
||||
NM_CHECK(nullptr, bwm, "could not get shared browser workflow manager pointer");
|
||||
}
|
||||
|
||||
BrowserWorkflowManager_openBrowser(bwm, modal, url, css);
|
||||
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_misc) {
|
||||
#define NM_ERR_RET nullptr
|
||||
if (!strcmp(arg, "home")) {
|
||||
//libnickel 4.6 * _ZN19StatusBarController4homeEv
|
||||
void (*StatusBarController_home)();
|
||||
reinterpret_cast<void*&>(StatusBarController_home) = dlsym(RTLD_DEFAULT, "_ZN19StatusBarController4homeEv");
|
||||
NM_ASSERT(StatusBarController_home, "could not dlsym StatusBarController::home");
|
||||
NM_CHECK(nullptr, StatusBarController_home, "could not dlsym StatusBarController::home");
|
||||
|
||||
StatusBarController_home();
|
||||
} else if (!strcmp(arg, "rescan_books")) {
|
||||
//libnickel 4.13.12638 * _ZN19PlugWorkflowManager14sharedInstanceEv
|
||||
PlugWorkflowManager *(*PlugWorkflowManager_sharedInstance)();
|
||||
reinterpret_cast<void*&>(PlugWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager14sharedInstanceEv");
|
||||
NM_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
|
||||
NM_CHECK(nullptr, PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
|
||||
|
||||
//libnickel 4.13.12638 * _ZN19PlugWorkflowManager4syncEv
|
||||
void (*PlugWorkflowManager_sync)(PlugWorkflowManager*);
|
||||
reinterpret_cast<void*&>(PlugWorkflowManager_sync) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager4syncEv");
|
||||
NM_ASSERT(PlugWorkflowManager_sync, "could not dlsym PlugWorkflowManager::sync");
|
||||
NM_CHECK(nullptr, PlugWorkflowManager_sync, "could not dlsym PlugWorkflowManager::sync");
|
||||
|
||||
PlugWorkflowManager *wf = PlugWorkflowManager_sharedInstance();
|
||||
NM_ASSERT(wf, "could not get shared PlugWorkflowManager pointer");
|
||||
NM_CHECK(nullptr, wf, "could not get shared PlugWorkflowManager pointer");
|
||||
|
||||
PlugWorkflowManager_sync(wf);
|
||||
} else if (!strcmp(arg, "rescan_books_full")) {
|
||||
//libnickel 4.13.12638 * _ZN19PlugWorkflowManager14sharedInstanceEv
|
||||
PlugWorkflowManager *(*PlugWorkflowManager_sharedInstance)();
|
||||
reinterpret_cast<void*&>(PlugWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager14sharedInstanceEv");
|
||||
NM_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
|
||||
NM_CHECK(nullptr, PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance");
|
||||
|
||||
// this is what is called by PlugWorkflowManager::plugged after confirmation
|
||||
//libnickel 4.13.12638 * _ZN19PlugWorkflowManager18onCancelAndConnectEv
|
||||
void (*PlugWorkflowManager_onCancelAndConnect)(PlugWorkflowManager*);
|
||||
reinterpret_cast<void*&>(PlugWorkflowManager_onCancelAndConnect) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager18onCancelAndConnectEv");
|
||||
NM_ASSERT(PlugWorkflowManager_onCancelAndConnect, "could not dlsym PlugWorkflowManager::onCancelAndConnect");
|
||||
NM_CHECK(nullptr, PlugWorkflowManager_onCancelAndConnect, "could not dlsym PlugWorkflowManager::onCancelAndConnect");
|
||||
|
||||
//libnickel 4.13.12638 * _ZN19PlugWorkflowManager9unpluggedEv
|
||||
void (*PlugWorkflowManager_unplugged)(PlugWorkflowManager*);
|
||||
reinterpret_cast<void*&>(PlugWorkflowManager_unplugged) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager9unpluggedEv");
|
||||
NM_ASSERT(PlugWorkflowManager_unplugged, "could not dlsym PlugWorkflowManager::unplugged");
|
||||
NM_CHECK(nullptr, PlugWorkflowManager_unplugged, "could not dlsym PlugWorkflowManager::unplugged");
|
||||
|
||||
PlugWorkflowManager *wf = PlugWorkflowManager_sharedInstance();
|
||||
NM_ASSERT(wf, "could not get shared PlugWorkflowManager pointer");
|
||||
NM_CHECK(nullptr, wf, "could not get shared PlugWorkflowManager pointer");
|
||||
|
||||
PlugWorkflowManager_onCancelAndConnect(wf);
|
||||
sleep(1);
|
||||
@@ -458,84 +448,80 @@ NM_ACTION_(nickel_misc) {
|
||||
} else if (!strcmp(arg, "force_usb_connection")) {
|
||||
// we could call libnickel directly, but I prefer not to
|
||||
FILE *nhs;
|
||||
NM_ASSERT((nhs = fopen("/tmp/nickel-hardware-status", "w")), "could not open nickel hardware status pipe: %s", strerror(errno));
|
||||
NM_CHECK(nullptr, (nhs = fopen("/tmp/nickel-hardware-status", "w")), "could not open nickel hardware status pipe: %s", strerror(errno));
|
||||
|
||||
const char *msg = "usb plug add";
|
||||
NM_ASSERT(fputs(msg, nhs) >= 0, "could not write message '%s' to pipe: %s", msg, strerror(errno));
|
||||
NM_CHECK(nullptr, fputs(msg, nhs) >= 0, "could not write message '%s' to pipe: %s", msg, strerror(errno));
|
||||
|
||||
fclose(nhs);
|
||||
} else {
|
||||
NM_RETURN_ERR("unknown action '%s'", arg);
|
||||
NM_ERR_RET(nullptr, "unknown action '%s'", arg);
|
||||
}
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(power) {
|
||||
#define NM_ERR_RET nullptr
|
||||
if (!strcmp(arg, "shutdown") || !strcmp(arg, "reboot")) {
|
||||
//libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager14sharedInstanceEv
|
||||
N3PowerWorkflowManager *(*N3PowerWorkflowManager_sharedInstance)();
|
||||
reinterpret_cast<void*&>(N3PowerWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager14sharedInstanceEv");
|
||||
NM_ASSERT(N3PowerWorkflowManager_sharedInstance, "could not dlsym N3PowerWorkflowManager::sharedInstance, so cannot perform action cleanly (if you must, report a bug and use cmd_spawn instead)");
|
||||
NM_CHECK(nullptr, N3PowerWorkflowManager_sharedInstance, "could not dlsym N3PowerWorkflowManager::sharedInstance, so cannot perform action cleanly (if you must, report a bug and use cmd_spawn instead)");
|
||||
|
||||
N3PowerWorkflowManager *pwm = N3PowerWorkflowManager_sharedInstance();
|
||||
NM_ASSERT(pwm, "could not get shared power manager pointer");
|
||||
NM_CHECK(nullptr, pwm, "could not get shared power manager pointer");
|
||||
|
||||
if (!strcmp(arg, "shutdown")) {
|
||||
//libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager8powerOffEb
|
||||
void (*N3PowerWorkflowManager_powerOff)(N3PowerWorkflowManager*, bool); // bool is for if it's due to low battery
|
||||
reinterpret_cast<void*&>(N3PowerWorkflowManager_powerOff) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager8powerOffEb");
|
||||
NM_ASSERT(N3PowerWorkflowManager_powerOff, "could not dlsym N3PowerWorkflowManager::powerOff");
|
||||
NM_CHECK(nullptr, N3PowerWorkflowManager_powerOff, "could not dlsym N3PowerWorkflowManager::powerOff");
|
||||
|
||||
N3PowerWorkflowManager_powerOff(pwm, false);
|
||||
NM_RETURN_OK(nm_action_result_toast("Shutting down..."));
|
||||
return nm_action_result_toast("Shutting down...");
|
||||
} else if (!strcmp(arg, "reboot")) {
|
||||
//libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager6rebootEv
|
||||
void (*N3PowerWorkflowManager_reboot)(N3PowerWorkflowManager*);
|
||||
reinterpret_cast<void*&>(N3PowerWorkflowManager_reboot) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager6rebootEv");
|
||||
NM_ASSERT(N3PowerWorkflowManager_reboot, "could not dlsym N3PowerWorkflowManager::reboot");
|
||||
NM_CHECK(nullptr, N3PowerWorkflowManager_reboot, "could not dlsym N3PowerWorkflowManager::reboot");
|
||||
|
||||
N3PowerWorkflowManager_reboot(pwm);
|
||||
NM_RETURN_OK(nm_action_result_toast("Rebooting..."));
|
||||
return nm_action_result_toast("Rebooting...");
|
||||
}
|
||||
} else {
|
||||
NM_RETURN_ERR("unknown power action '%s'", arg);
|
||||
NM_ERR_RET(nullptr, "unknown power action '%s'", arg);
|
||||
}
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(nickel_wifi) {
|
||||
#define NM_ERR_RET nullptr
|
||||
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager14sharedInstanceEv
|
||||
WirelessWorkflowManager *(*WirelessWorkflowManager_sharedInstance)();
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager14sharedInstanceEv");
|
||||
NM_ASSERT(WirelessWorkflowManager_sharedInstance, "could not dlsym WirelessWorkflowManager::sharedInstance");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_sharedInstance, "could not dlsym WirelessWorkflowManager::sharedInstance");
|
||||
|
||||
WirelessWorkflowManager *wfm = WirelessWorkflowManager_sharedInstance();
|
||||
NM_ASSERT(wfm, "could not get shared wireless manager pointer");
|
||||
NM_CHECK(nullptr, wfm, "could not get shared wireless manager pointer");
|
||||
|
||||
if (!strcmp(arg, "autoconnect")) {
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager15connectWirelessEbb
|
||||
void (*WirelessWorkflowManager_connectWireless)(WirelessWorkflowManager*, bool, bool); // I haven't looked into what the params are for, so I'm just using what the browser uses when opening it
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_connectWireless) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager15connectWirelessEbb");
|
||||
NM_ASSERT(WirelessWorkflowManager_connectWireless, "could not dlsym WirelessWorkflowManager::connectWireless");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_connectWireless, "could not dlsym WirelessWorkflowManager::connectWireless");
|
||||
|
||||
WirelessWorkflowManager_connectWireless(wfm, true, false);
|
||||
} else if (!strcmp(arg, "autoconnect_silent")) {
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager23connectWirelessSilentlyEv
|
||||
void (*WirelessWorkflowManager_connectWirelessSilently)(WirelessWorkflowManager*);
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_connectWirelessSilently) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager23connectWirelessSilentlyEv");
|
||||
NM_ASSERT(WirelessWorkflowManager_connectWirelessSilently, "could not dlsym WirelessWorkflowManager::connectWirelessSilently");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_connectWirelessSilently, "could not dlsym WirelessWorkflowManager::connectWirelessSilently");
|
||||
|
||||
WirelessWorkflowManager_connectWirelessSilently(wfm);
|
||||
} else if (!strcmp(arg, "enable") || !strcmp(arg, "disable") || !strcmp(arg, "toggle")) {
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager14isAirplaneModeEv
|
||||
bool (*WirelessWorkflowManager_isAirplaneMode)(WirelessWorkflowManager*);
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_isAirplaneMode) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager14isAirplaneModeEv");
|
||||
NM_ASSERT(WirelessWorkflowManager_isAirplaneMode, "could not dlsym WirelessWorkflowManager::isAirplaneMode");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_isAirplaneMode, "could not dlsym WirelessWorkflowManager::isAirplaneMode");
|
||||
|
||||
bool e = WirelessWorkflowManager_isAirplaneMode(wfm);
|
||||
NM_LOG("wifi disabled: %d", e);
|
||||
@@ -543,7 +529,7 @@ NM_ACTION_(nickel_wifi) {
|
||||
//libnickel 4.6 * _ZN23WirelessWorkflowManager15setAirplaneModeEb
|
||||
void (*WirelessWorkflowManager_setAirplaneMode)(WirelessWorkflowManager*, bool);
|
||||
reinterpret_cast<void*&>(WirelessWorkflowManager_setAirplaneMode) = dlsym(RTLD_DEFAULT, "_ZN23WirelessWorkflowManager15setAirplaneModeEb");
|
||||
NM_ASSERT(WirelessWorkflowManager_setAirplaneMode, "could not dlsym WirelessWorkflowManager::setAirplaneMode");
|
||||
NM_CHECK(nullptr, WirelessWorkflowManager_setAirplaneMode, "could not dlsym WirelessWorkflowManager::setAirplaneMode");
|
||||
|
||||
if (!strcmp(arg, "enable")) {
|
||||
if (e)
|
||||
@@ -555,15 +541,13 @@ NM_ACTION_(nickel_wifi) {
|
||||
WirelessWorkflowManager_setAirplaneMode(wfm, !e);
|
||||
}
|
||||
} else {
|
||||
NM_RETURN_ERR("unknown wifi action '%s'", arg);
|
||||
NM_ERR_RET(nullptr, "unknown wifi action '%s'", arg);
|
||||
}
|
||||
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
#undef NM_ERR_RET
|
||||
return nm_action_result_silent();
|
||||
}
|
||||
|
||||
NM_ACTION_(cmd_spawn) {
|
||||
#define NM_ERR_RET nullptr
|
||||
char *tmp = strdup(arg); // strsep and strtrim will modify it
|
||||
char *tmp1 = tmp; // so we can still free tmp later
|
||||
char *tmp2 = strtrim(strsep(&tmp1, ":")); // get the part before the : into tmp2, if any
|
||||
@@ -587,13 +571,13 @@ NM_ACTION_(cmd_spawn) {
|
||||
|
||||
free(tmp);
|
||||
|
||||
NM_ASSERT(ok, "could not start process");
|
||||
NM_RETURN_OK(quiet ? nm_action_result_silent() : nm_action_result_toast("Successfully started process with PID %lu.", (unsigned long)(pid)));
|
||||
#undef NM_ERR_RET
|
||||
NM_CHECK(nullptr, ok, "could not start process");
|
||||
return quiet
|
||||
? nm_action_result_silent()
|
||||
: nm_action_result_toast("Successfully started process with PID %lu.", (unsigned long)(pid));
|
||||
}
|
||||
|
||||
NM_ACTION_(cmd_output) {
|
||||
#define NM_ERR_RET nullptr
|
||||
|
||||
// split the timeout into timeout, put the command into cmd
|
||||
char *tmp = strdup(arg);
|
||||
@@ -601,7 +585,7 @@ NM_ACTION_(cmd_output) {
|
||||
char *tmp1 = strtrim(strsep(&cmd, ":")), *tmp2;
|
||||
long timeout = strtol(tmp1, &tmp2, 10);
|
||||
cmd = strtrim(cmd);
|
||||
NM_ASSERT(*tmp1 && !*tmp2 && timeout > 0 && timeout < 10000, "invalid timeout '%s'", tmp1);
|
||||
NM_CHECK(nullptr, *tmp1 && !*tmp2 && timeout > 0 && timeout < 10000, "invalid timeout '%s'", tmp1);
|
||||
|
||||
// parse the quiet option and update cmd if it's specified
|
||||
char *tmp3 = strdup(cmd);
|
||||
@@ -630,23 +614,24 @@ NM_ACTION_(cmd_output) {
|
||||
if (!ok) {
|
||||
switch (proc.error()) {
|
||||
case QProcess::Timedout:
|
||||
NM_RETURN_ERR("could not run process: timed out");
|
||||
NM_ERR_RET(nullptr, "could not run process: timed out");
|
||||
case QProcess::FailedToStart:
|
||||
NM_RETURN_ERR("could not run process: missing program or wrong permissions");
|
||||
NM_ERR_RET(nullptr, "could not run process: missing program or wrong permissions");
|
||||
case QProcess::Crashed:
|
||||
NM_RETURN_ERR("could not run process: process crashed");
|
||||
NM_ERR_RET(nullptr, "could not run process: process crashed");
|
||||
default:
|
||||
NM_RETURN_ERR("could not run process");
|
||||
NM_ERR_RET(nullptr, "could not run process");
|
||||
}
|
||||
}
|
||||
|
||||
if (proc.exitStatus() == QProcess::NormalExit && proc.exitCode() != 0)
|
||||
NM_RETURN_ERR("could not run process: process exited with status %d", proc.exitCode());
|
||||
NM_ERR_RET(nullptr, "could not run process: process exited with status %d", proc.exitCode());
|
||||
|
||||
QString out = proc.readAllStandardOutput();
|
||||
if (out.length() > 500)
|
||||
out = out.left(500) + "...";
|
||||
|
||||
NM_RETURN_OK(quiet ? nm_action_result_silent() : nm_action_result_msg("%s", qPrintable(out)));
|
||||
|
||||
#undef NM_ERR_RET
|
||||
return quiet
|
||||
? nm_action_result_silent()
|
||||
: nm_action_result_msg("%s", qPrintable(out));
|
||||
}
|
||||
|
||||
153
src/config.c
153
src/config.c
@@ -48,14 +48,12 @@ static int nm_config_files_filter(const struct dirent *de) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
nm_config_file_t *nm_config_files(char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
nm_config_file_t *nm_config_files() {
|
||||
nm_config_file_t *cfs = NULL, *cfc = NULL;
|
||||
|
||||
struct dirent **nl;
|
||||
int n = scandir(NM_CONFIG_DIR, &nl, nm_config_files_filter, alphasort);
|
||||
NM_ASSERT(n != -1, "could not scan config dir: %s", strerror(errno));
|
||||
NM_CHECK(NULL, n != -1, "could not scan config dir: %s", strerror(errno));
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
struct dirent *de = nl[i];
|
||||
@@ -72,9 +70,9 @@ nm_config_file_t *nm_config_files(char **err_out) {
|
||||
cfs = tmp;
|
||||
}
|
||||
if (!fn)
|
||||
NM_RETURN_ERR("could not build full path for config file");
|
||||
NM_ERR_RET(NULL, "could not build full path for config file");
|
||||
free(fn);
|
||||
NM_RETURN_ERR("could not stat %s/%s", NM_CONFIG_DIR, de->d_name);
|
||||
NM_ERR_RET(NULL, "could not stat %s/%s", NM_CONFIG_DIR, de->d_name);
|
||||
}
|
||||
|
||||
// skip it if it isn't a file
|
||||
@@ -100,21 +98,19 @@ nm_config_file_t *nm_config_files(char **err_out) {
|
||||
|
||||
free(nl);
|
||||
|
||||
NM_RETURN_OK(cfs);
|
||||
#undef NM_ERR_RET
|
||||
return cfs;
|
||||
}
|
||||
|
||||
bool nm_config_files_update(nm_config_file_t **files, char **err_out) {
|
||||
#define NM_ERR_RET false
|
||||
NM_ASSERT(files, "files pointer must not be null");
|
||||
int nm_config_files_update(nm_config_file_t **files) {
|
||||
NM_CHECK(false, files, "files pointer must not be null");
|
||||
|
||||
nm_config_file_t *nfiles = nm_config_files(err_out);
|
||||
if (*err_out)
|
||||
return NM_ERR_RET;
|
||||
nm_config_file_t *nfiles = nm_config_files();
|
||||
if (nm_err_peek())
|
||||
return -1; // the error is passed on
|
||||
|
||||
if (!*files) {
|
||||
*files = nfiles;
|
||||
NM_RETURN_OK(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ch = false;
|
||||
@@ -133,13 +129,11 @@ bool nm_config_files_update(nm_config_file_t **files, char **err_out) {
|
||||
if (ch || op || np) {
|
||||
nm_config_files_free(*files);
|
||||
*files = nfiles;
|
||||
NM_RETURN_OK(true);
|
||||
return 0;
|
||||
} else {
|
||||
nm_config_files_free(nfiles);
|
||||
NM_RETURN_OK(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#undef NM_ERR_RET
|
||||
}
|
||||
|
||||
void nm_config_files_free(nm_config_file_t *files) {
|
||||
@@ -206,20 +200,18 @@ static const char* nm_config_parse__strerror(nm_config_parse__append__ret_t ret)
|
||||
|
||||
// note: line must point to the part after the config line type, and will be
|
||||
// modified. if the config line type doesn't match, everything will be
|
||||
// left as-is and false will be returned without an error. if an error
|
||||
// occurs, err_out will be set and true will be returned (since stuff
|
||||
// may be modified). otherwise, true is returned without an error (the
|
||||
// left as-is and false will be returned with nm_err cleared. if an error
|
||||
// occurs, nm_err will be set and true will be returned (since stuff
|
||||
// may be modified). otherwise, true is returned with nm_err cleared (the
|
||||
// parsed output will have strings pointing directly into the line).
|
||||
|
||||
static bool nm_config_parse__lineend_action(int field, char **line, bool p_on_success, bool p_on_failure, nm_menu_action_t *act_out, char **err_out);
|
||||
static bool nm_config_parse__line_item(const char *type, char **line, nm_menu_item_t *it_out, nm_menu_action_t *action_out, char **err_out);
|
||||
static bool nm_config_parse__line_chain(const char *type, char **line, nm_menu_action_t *act_out, char **err_out);
|
||||
static bool nm_config_parse__line_generator(const char *type, char **line, nm_generator_t *gn_out, char **err_out);
|
||||
static bool nm_config_parse__lineend_action(int field, char **line, bool p_on_success, bool p_on_failure, nm_menu_action_t *act_out);
|
||||
static bool nm_config_parse__line_item(const char *type, char **line, nm_menu_item_t *it_out, nm_menu_action_t *action_out);
|
||||
static bool nm_config_parse__line_chain(const char *type, char **line, nm_menu_action_t *act_out);
|
||||
static bool nm_config_parse__line_generator(const char *type, char **line, nm_generator_t *gn_out);
|
||||
|
||||
nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
char *err = NULL;
|
||||
nm_config_t *nm_config_parse(nm_config_file_t *files) {
|
||||
const char *err = NULL;
|
||||
|
||||
FILE *cfgfile = NULL;
|
||||
char *line = NULL;
|
||||
@@ -237,12 +229,9 @@ nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out) {
|
||||
#define RETERR(fmt, ...) do { \
|
||||
if (cfgfile) \
|
||||
fclose(cfgfile); \
|
||||
if (err_out) \
|
||||
asprintf(err_out, fmt, ##__VA_ARGS__); \
|
||||
free(err); \
|
||||
free(line); \
|
||||
nm_config_free(state.cfg_c); \
|
||||
return NM_ERR_RET; \
|
||||
NM_ERR_RET(NULL, fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
for (nm_config_file_t *cf = files; cf; cf = cf->next) {
|
||||
@@ -263,8 +252,8 @@ nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out) {
|
||||
|
||||
char *s_typ = strtrim(strsep(&cur, ":"));
|
||||
|
||||
if (nm_config_parse__line_item(s_typ, &cur, &tmp_it, &tmp_act, &err)) {
|
||||
if (err)
|
||||
if (nm_config_parse__line_item(s_typ, &cur, &tmp_it, &tmp_act)) {
|
||||
if ((err = nm_err()))
|
||||
RETERR("file %s: line %d: parse menu_item: %s", cf->path, line_n, err);
|
||||
if ((ret = nm_config_parse__append_item(&state, &tmp_it)))
|
||||
RETERR("file %s: line %d: error appending item to config: %s", cf->path, line_n, nm_config_parse__strerror(ret));
|
||||
@@ -273,16 +262,16 @@ nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nm_config_parse__line_chain(s_typ, &cur, &tmp_act, &err)) {
|
||||
if (err)
|
||||
if (nm_config_parse__line_chain(s_typ, &cur, &tmp_act)) {
|
||||
if ((err = nm_err()))
|
||||
RETERR("file %s: line %d: parse chain: %s", cf->path, line_n, err);
|
||||
if ((ret = nm_config_parse__append_action(&state, &tmp_act)))
|
||||
RETERR("file %s: line %d: error appending action to config: %s", cf->path, line_n, nm_config_parse__strerror(ret));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nm_config_parse__line_generator(s_typ, &cur, &tmp_gn, &err)) {
|
||||
if (err)
|
||||
if (nm_config_parse__line_generator(s_typ, &cur, &tmp_gn)) {
|
||||
if ((err = nm_err()))
|
||||
RETERR("file %s: line %d: parse generator: %s", cf->path, line_n, err);
|
||||
if ((ret = nm_config_parse__append_generator(&state, &tmp_gn)))
|
||||
RETERR("file %s: line %d: error appending generator to config: %s", cf->path, line_n, nm_config_parse__strerror(ret));
|
||||
@@ -341,41 +330,35 @@ nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out) {
|
||||
if (rm > NM_CONFIG_MAX_MENU_ITEMS_PER_MENU)
|
||||
RETERR("too many menu items in reader menu (> %d)", NM_CONFIG_MAX_MENU_ITEMS_PER_MENU);
|
||||
|
||||
NM_RETURN_OK(state.cfg_s);
|
||||
#undef NM_ERR_RET
|
||||
return state.cfg_s;
|
||||
}
|
||||
|
||||
static bool nm_config_parse__line_item(const char *type, char **line, nm_menu_item_t *it_out, nm_menu_action_t *action_out, char **err_out) {
|
||||
#define NM_ERR_RET true
|
||||
|
||||
if (strcmp(type, "menu_item"))
|
||||
NM_RETURN_OK(false);
|
||||
static bool nm_config_parse__line_item(const char *type, char **line, nm_menu_item_t *it_out, nm_menu_action_t *action_out) {
|
||||
if (strcmp(type, "menu_item")) {
|
||||
nm_err_set(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
*it_out = (nm_menu_item_t){0};
|
||||
|
||||
char *s_loc = strtrim(strsep(line, ":"));
|
||||
if (!s_loc) NM_RETURN_ERR("field 2: expected location, got end of line");
|
||||
if (!s_loc) NM_ERR_RET(NULL, "field 2: expected location, got end of line");
|
||||
else if (!strcmp(s_loc, "main")) it_out->loc = NM_MENU_LOCATION_MAIN_MENU;
|
||||
else if (!strcmp(s_loc, "reader")) it_out->loc = NM_MENU_LOCATION_READER_MENU;
|
||||
else NM_RETURN_ERR("field 2: unknown location '%s'", s_loc);
|
||||
else NM_ERR_RET(NULL, "field 2: unknown location '%s'", s_loc);
|
||||
|
||||
char *p_lbl = strtrim(strsep(line, ":"));
|
||||
if (!p_lbl) NM_RETURN_ERR("field 3: expected label, got end of line");
|
||||
if (!p_lbl) NM_ERR_RET(NULL, "field 3: expected label, got end of line");
|
||||
it_out->lbl = p_lbl;
|
||||
|
||||
nm_config_parse__lineend_action(4, line, true, true, action_out, err_out);
|
||||
if (*err_out)
|
||||
return NM_ERR_RET;
|
||||
|
||||
NM_RETURN_OK(true);
|
||||
#undef NM_ERR_RET
|
||||
return nm_config_parse__lineend_action(4, line, true, true, action_out);
|
||||
}
|
||||
|
||||
static bool nm_config_parse__line_chain(const char *type, char **line, nm_menu_action_t *act_out, char **err_out) {
|
||||
#define NM_ERR_RET true
|
||||
|
||||
if (strncmp(type, "chain_", 5))
|
||||
NM_RETURN_OK(false);
|
||||
static bool nm_config_parse__line_chain(const char *type, char **line, nm_menu_action_t *act_out) {
|
||||
if (strncmp(type, "chain_", 5)) {
|
||||
nm_err_set(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool p_on_success, p_on_failure;
|
||||
if (!strcmp(type, "chain_success")) {
|
||||
@@ -387,70 +370,66 @@ static bool nm_config_parse__line_chain(const char *type, char **line, nm_menu_a
|
||||
} else if (!strcmp(type, "chain_failure")) {
|
||||
p_on_success = false;
|
||||
p_on_failure = true;
|
||||
} else NM_RETURN_OK(false);
|
||||
} else {
|
||||
nm_err_set(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
nm_config_parse__lineend_action(2, line, p_on_success, p_on_failure, act_out, err_out);
|
||||
if (*err_out)
|
||||
return NM_ERR_RET;
|
||||
|
||||
NM_RETURN_OK(true);
|
||||
#undef NM_ERR_RET
|
||||
return nm_config_parse__lineend_action(2, line, p_on_success, p_on_failure, act_out);
|
||||
}
|
||||
|
||||
static bool nm_config_parse__line_generator(const char *type, char **line, nm_generator_t *gn_out, char **err_out) {
|
||||
#define NM_ERR_RET true
|
||||
|
||||
if (strcmp(type, "generator"))
|
||||
NM_RETURN_OK(false);
|
||||
static bool nm_config_parse__line_generator(const char *type, char **line, nm_generator_t *gn_out) {
|
||||
if (strcmp(type, "generator")) {
|
||||
nm_err_set(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
*gn_out = (nm_generator_t){0};
|
||||
|
||||
char *s_loc = strtrim(strsep(line, ":"));
|
||||
if (!s_loc) NM_RETURN_ERR("field 2: expected location, got end of line");
|
||||
if (!s_loc) NM_ERR_RET(NULL, "field 2: expected location, got end of line");
|
||||
else if (!strcmp(s_loc, "main")) gn_out->loc = NM_MENU_LOCATION_MAIN_MENU;
|
||||
else if (!strcmp(s_loc, "reader")) gn_out->loc = NM_MENU_LOCATION_READER_MENU;
|
||||
else NM_RETURN_ERR("field 2: unknown location '%s'", s_loc);
|
||||
else NM_ERR_RET(NULL, "field 2: unknown location '%s'", s_loc);
|
||||
|
||||
char *s_generate = strtrim(strsep(line, ":"));
|
||||
if (!s_generate) NM_RETURN_ERR("field 3: expected generator, got end of line");
|
||||
if (!s_generate) NM_ERR_RET(NULL, "field 3: expected generator, got end of line");
|
||||
#define X(name) \
|
||||
else if (!strcmp(s_generate, #name)) gn_out->generate = NM_GENERATOR(name);
|
||||
NM_GENERATORS
|
||||
#undef X
|
||||
else NM_RETURN_ERR("field 3: unknown generator '%s'", s_generate);
|
||||
else NM_ERR_RET(NULL, "field 3: unknown generator '%s'", s_generate);
|
||||
|
||||
char *p_arg = strtrim(*line); // note: optional
|
||||
if (p_arg) gn_out->arg = p_arg;
|
||||
|
||||
gn_out->desc = s_generate;
|
||||
|
||||
NM_RETURN_OK(true);
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool nm_config_parse__lineend_action(int field, char **line, bool p_on_success, bool p_on_failure, nm_menu_action_t *act_out, char **err_out) {
|
||||
#define NM_ERR_RET true
|
||||
|
||||
static bool nm_config_parse__lineend_action(int field, char **line, bool p_on_success, bool p_on_failure, nm_menu_action_t *act_out) {
|
||||
*act_out = (nm_menu_action_t){0};
|
||||
|
||||
char *s_act = strtrim(strsep(line, ":"));
|
||||
if (!s_act) NM_RETURN_ERR("field %d: expected action, got end of line", field);
|
||||
if (!s_act) NM_ERR_RET(true, "field %d: expected action, got end of line", field);
|
||||
#define X(name) \
|
||||
else if (!strcmp(s_act, #name)) act_out->act = NM_ACTION(name);
|
||||
NM_ACTIONS
|
||||
#undef X
|
||||
else NM_RETURN_ERR("field %d: unknown action '%s'", field, s_act);
|
||||
else NM_ERR_RET(true, "field %d: unknown action '%s'", field, s_act);
|
||||
|
||||
// type: menu_item - field 5: argument
|
||||
char *p_arg = strtrim(*line);
|
||||
if (!p_arg) NM_RETURN_ERR("field %d: expected argument, got end of line\n", field+1);
|
||||
if (!p_arg) NM_ERR_RET(true, "field %d: expected argument, got end of line\n", field+1);
|
||||
act_out->arg = p_arg;
|
||||
|
||||
act_out->on_success = p_on_success;
|
||||
act_out->on_failure = p_on_failure;
|
||||
|
||||
NM_RETURN_OK(true);
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static nm_config_parse__append__ret_t nm_config_parse__append_item(nm_config_parse__state_t *restrict state, nm_menu_item_t *const restrict it) {
|
||||
|
||||
25
src/config.h
25
src/config.h
@@ -19,24 +19,25 @@ typedef struct nm_config_t nm_config_t;
|
||||
|
||||
typedef struct nm_config_file_t nm_config_file_t;
|
||||
|
||||
// nm_config_parse lists the configuration files in /mnt/onboard/.adds/nm. An
|
||||
// error is returned if there are errors reading the dir.
|
||||
nm_config_file_t *nm_config_files(char **err_out);
|
||||
// nm_config_parse lists the configuration files in /mnt/onboard/.adds/nm. If
|
||||
// there are errors reading the dir, NULL is returned and nm_err is set.
|
||||
nm_config_file_t *nm_config_files();
|
||||
|
||||
// nm_config_files_update checks if the configuration files are up to date and
|
||||
// updates them, returning true, if not. If *files is NULL, it is equivalent to
|
||||
// doing `*files = nm_config_files(err_out)`. If an error occurs, the pointer is
|
||||
// left untouched and false is returned along with the error. Warning: if the
|
||||
// files have changed, the pointer passed to files will become invalid (it gets
|
||||
// replaced).
|
||||
bool nm_config_files_update(nm_config_file_t **files, char **err_out);
|
||||
// updates them. If the files are already up-to-date, 1 is returned. If the
|
||||
// files were updated, 0 is returned. If an error occurs, the pointer is left
|
||||
// untouched and -1 is returned with nm_err set. Warning: if the files have
|
||||
// changed, the pointer passed to files will become invalid (it gets replaced).
|
||||
// If *files is NULL, it is equivalent to doing `*files = nm_config_files()`.
|
||||
int nm_config_files_update(nm_config_file_t **files);
|
||||
|
||||
// nm_config_files_free frees the list of configuration files.
|
||||
void nm_config_files_free(nm_config_file_t *files);
|
||||
|
||||
// nm_config_parse parses the configuration files. An error is returned if there
|
||||
// are syntax errors, file access errors, or invalid action names for menu_item.
|
||||
nm_config_t *nm_config_parse(nm_config_file_t *files, char **err_out);
|
||||
// nm_config_parse parses the configuration files. If there are syntax errors,
|
||||
// file access errors, or invalid action names for menu_item, then NULL is
|
||||
// returned and nm_err is set. On success, the config is returned.
|
||||
nm_config_t *nm_config_parse(nm_config_file_t *files);
|
||||
|
||||
// nm_config_generate runs all generators synchronously and sequentially. Any
|
||||
// previously generated items are automatically removed if updates are required.
|
||||
|
||||
37
src/dlhook.c
37
src/dlhook.c
@@ -28,14 +28,12 @@
|
||||
// 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, char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
NM_ASSERT(handle && symname && target, "BUG: required arguments are null");
|
||||
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_ASSERT(!dlinfo(handle, RTLD_DI_LINKMAP, &lm), "could not get link_map for lib");
|
||||
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
|
||||
@@ -65,9 +63,9 @@ void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out)
|
||||
}
|
||||
}
|
||||
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_ASSERT(dyn.plt_ent_sz, "plt_ent_sz is zero");
|
||||
NM_ASSERT(dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
|
||||
NM_ASSERT((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);
|
||||
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++) {
|
||||
@@ -75,7 +73,7 @@ void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out)
|
||||
ElfW(Rel) *rel = dyn.plt_is_rela
|
||||
? (ElfW(Rel)*)(&dyn.plt.rela[i])
|
||||
: &dyn.plt.rel[i];
|
||||
NM_ASSERT(ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info));
|
||||
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];
|
||||
@@ -85,14 +83,13 @@ void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out)
|
||||
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_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset));
|
||||
NM_ASSERT(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_ASSERT(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_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));
|
||||
|
||||
// TODO: figure out why directly getting the offset from the GOT was broken on ARM, but not x86
|
||||
NM_LOG("ensuring the symbol is loaded");
|
||||
NM_LOG("ensuring the symbol is loaded (to get the original address)");
|
||||
void *orig = dlsym(handle, symname);
|
||||
NM_ASSERT(orig, "could not dlsym symbol");
|
||||
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)
|
||||
@@ -100,18 +97,18 @@ void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out)
|
||||
// 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_ASSERT(pagesize != -1, "could not get memory page size");
|
||||
NM_CHECK(NULL, pagesize != -1, "could not get memory page size");
|
||||
void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1));
|
||||
NM_ASSERT(!mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff);
|
||||
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);
|
||||
NM_RETURN_OK(orig);
|
||||
return orig;
|
||||
}
|
||||
|
||||
NM_RETURN_ERR("could not find symbol");
|
||||
#undef NM_err_ret
|
||||
NM_ERR_SET("could not find symbol");
|
||||
return 0;
|
||||
}
|
||||
|
||||
10
src/dlhook.h
10
src/dlhook.h
@@ -8,11 +8,11 @@ extern "C" {
|
||||
|
||||
// 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). If
|
||||
// an error occurs, NULL is returned and if err is a valid pointer, it is set to
|
||||
// a malloc'd string describing it. This function requires glibc and Linux. It
|
||||
// should work on any architecture, and it should be resilient to most errors.
|
||||
NM_PRIVATE void *nm_dlhook(void *handle, const char *symname, void *target, char **err_out);
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -16,30 +16,27 @@ struct nm_failsafe_t {
|
||||
int delay;
|
||||
};
|
||||
|
||||
nm_failsafe_t *nm_failsafe_create(char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
nm_failsafe_t *nm_failsafe_create() {
|
||||
NM_LOG("failsafe: allocating memory");
|
||||
nm_failsafe_t *fs;
|
||||
NM_ASSERT((fs = calloc(1, sizeof(nm_failsafe_t))), "could not allocate memory");
|
||||
NM_CHECK(NULL, (fs = calloc(1, sizeof(nm_failsafe_t))), "could not allocate memory");
|
||||
|
||||
NM_LOG("failsafe: finding filenames");
|
||||
Dl_info info;
|
||||
NM_ASSERT(dladdr(nm_failsafe_create, &info), "could not find own path");
|
||||
NM_ASSERT(info.dli_fname, "dladdr did not return a filename");
|
||||
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_ASSERT(!(d && !strcmp(d, ".failsafe")), "lib was loaded from the failsafe for some reason");
|
||||
NM_ASSERT((fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname);
|
||||
NM_ASSERT(asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename");
|
||||
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_ASSERT(dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self");
|
||||
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_ASSERT(!rename(fs->orig, fs->tmp), "could not rename lib");
|
||||
NM_CHECK(NULL, !rename(fs->orig, fs->tmp), "could not rename lib");
|
||||
|
||||
NM_RETURN_OK(fs);
|
||||
#undef NM_ERR_RET
|
||||
return fs;
|
||||
}
|
||||
|
||||
static void *_nm_failsafe_destroy(void* _fs) {
|
||||
|
||||
@@ -14,8 +14,9 @@ extern "C" {
|
||||
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.
|
||||
NM_PRIVATE nm_failsafe_t *nm_failsafe_create(char **err_out);
|
||||
// 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.
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) {
|
||||
NM_LOG("generator: running generator (%s) (%s) (%d) (%p)", gen->desc, gen->arg, gen->loc, gen->generate);
|
||||
|
||||
char *err;
|
||||
struct timespec old = gen->time;
|
||||
size_t sz = (size_t)(-1); // this should always be set by generate upon success, but we'll initialize it just in case
|
||||
nm_menu_item_t **items = gen->generate(gen->arg, &gen->time, &sz, &err);
|
||||
nm_menu_item_t **items = gen->generate(gen->arg, &gen->time, &sz);
|
||||
|
||||
if (items && old.tv_sec == gen->time.tv_sec && old.tv_nsec == gen->time.tv_nsec)
|
||||
NM_LOG("generator: bug: new items were returned, but time wasn't changed");
|
||||
|
||||
const char *err = nm_err();
|
||||
|
||||
if (!old.tv_sec && !old.tv_nsec && !err && !items)
|
||||
NM_LOG("generator: warning: no existing items (time == 0), but no new items or error were returned");
|
||||
|
||||
@@ -39,7 +40,6 @@ nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out) {
|
||||
asprintf(&items[0]->action->arg, "%s: %s", gen->desc, err);
|
||||
items[0]->action->on_failure = true;
|
||||
items[0]->action->on_success = true;
|
||||
free(err);
|
||||
}
|
||||
|
||||
if (!err && !items && (old.tv_sec != gen->time.tv_sec || old.tv_nsec != gen->time.tv_nsec))
|
||||
|
||||
@@ -10,10 +10,10 @@ extern "C" {
|
||||
|
||||
// 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
|
||||
// out_sz. The menu item locations must not be set. On error, err_out must be
|
||||
// set if provided, NULL must be returned, and sz_out is undefined. If no
|
||||
// entries are generated, NULL must be returned with sz_out set to 0. All
|
||||
// strings should also be malloc'd.
|
||||
// out_sz. The menu item locations must not be set. On error, nm_err must be
|
||||
// set, NULL must be returned, and sz_out is undefined. If no entries are
|
||||
// generated, NULL must be returned with sz_out set to 0. All strings should
|
||||
// also be malloc'd. On success, nm_err must be cleared.
|
||||
//
|
||||
// time_in_out will not be NULL, and contains zero or the last modification time
|
||||
// for the generator. If it is zero, the generator should generate the items as
|
||||
@@ -25,7 +25,7 @@ extern "C" {
|
||||
// if the time is zero, and return NULL if the time is nonzero. Note that this
|
||||
// time doesn't have to account for different arguments or multiple instances,
|
||||
// as changes in those will always cause the time to be set to zero.
|
||||
typedef nm_menu_item_t **(*nm_generator_fn_t)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out);
|
||||
typedef nm_menu_item_t **(*nm_generator_fn_t)(const char *arg, struct timespec *time_in_out, size_t *sz_out);
|
||||
|
||||
typedef struct {
|
||||
char *desc; // only used for making the errors more meaningful (it is the title)
|
||||
@@ -44,9 +44,9 @@ nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out);
|
||||
#define NM_GENERATOR(name) nm_generator_##name
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define NM_GENERATOR_(name) extern "C" nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out)
|
||||
#define NM_GENERATOR_(name) extern "C" nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out)
|
||||
#else
|
||||
#define NM_GENERATOR_(name) nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out, char **err_out)
|
||||
#define NM_GENERATOR_(name) nm_menu_item_t **NM_GENERATOR(name)(const char *arg, struct timespec *time_in_out, size_t *sz_out)
|
||||
#endif
|
||||
|
||||
#define NM_GENERATORS \
|
||||
|
||||
@@ -15,18 +15,19 @@
|
||||
#include "util.h"
|
||||
|
||||
NM_GENERATOR_(_test) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
if (time_in_out->tv_sec || time_in_out->tv_nsec)
|
||||
NM_RETURN_OK(NULL); // updates not supported (or needed, for that matter)
|
||||
if (time_in_out->tv_sec || time_in_out->tv_nsec) {
|
||||
nm_err_set(NULL);
|
||||
return NULL; // updates not supported (or needed, for that matter)
|
||||
}
|
||||
|
||||
char *tmp;
|
||||
long n = strtol(arg, &tmp, 10);
|
||||
NM_ASSERT(*arg && !*tmp && n >= 0 && n <= 10, "invalid count '%s': must be an integer from 1-10", arg);
|
||||
NM_CHECK(NULL, *arg && !*tmp && n >= 0 && n <= 10, "invalid count '%s': must be an integer from 1-10", arg);
|
||||
|
||||
if (n == 0) {
|
||||
*sz_out = 0;
|
||||
NM_RETURN_OK(NULL);
|
||||
nm_err_set(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nm_menu_item_t **items = calloc(n, sizeof(nm_menu_item_t*));
|
||||
@@ -43,16 +44,13 @@ NM_GENERATOR_(_test) {
|
||||
clock_gettime(CLOCK_REALTIME, time_in_out); // note: any nonzero value would work, but this generator is for testing and as an example
|
||||
|
||||
*sz_out = n;
|
||||
NM_RETURN_OK(items);
|
||||
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return items;
|
||||
}
|
||||
|
||||
NM_GENERATOR_(_test_time) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
if (arg && *arg)
|
||||
NM_RETURN_ERR("_test_time does not accept any arguments");
|
||||
NM_ERR_RET(NULL, "_test_time does not accept any arguments");
|
||||
|
||||
// note: this used as an example and for testing
|
||||
|
||||
@@ -66,7 +64,8 @@ NM_GENERATOR_(_test_time) {
|
||||
|
||||
if (time_in_out->tv_sec && ts.tv_sec - time_in_out->tv_sec < 10) {
|
||||
NM_LOG("_test_time: last update is nonzero and last update time is < 10s, skipping");
|
||||
NM_RETURN_OK(NULL);
|
||||
nm_err_set(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NM_LOG("_test_time: updating");
|
||||
@@ -85,20 +84,19 @@ NM_GENERATOR_(_test_time) {
|
||||
time_in_out->tv_sec = ts.tv_sec;
|
||||
|
||||
*sz_out = 1;
|
||||
NM_RETURN_OK(items);
|
||||
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return items;
|
||||
}
|
||||
|
||||
NM_GENERATOR_(kfmon) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
struct stat sb;
|
||||
if (stat(KFMON_IPC_SOCKET, &sb))
|
||||
NM_RETURN_ERR("error checking '%s': stat: %s", KFMON_IPC_SOCKET, strerror(errno));
|
||||
NM_ERR_RET(NULL, "error checking '%s': stat: %s", KFMON_IPC_SOCKET, strerror(errno));
|
||||
|
||||
if (time_in_out->tv_sec == sb.st_mtim.tv_sec && time_in_out->tv_nsec == sb.st_mtim.tv_nsec)
|
||||
NM_RETURN_OK(NULL);
|
||||
if (time_in_out->tv_sec == sb.st_mtim.tv_sec && time_in_out->tv_nsec == sb.st_mtim.tv_nsec) {
|
||||
nm_err_set(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Default with no arg or an empty arg is to request a gui-listing
|
||||
const char *kfmon_cmd = NULL;
|
||||
@@ -107,7 +105,7 @@ NM_GENERATOR_(kfmon) {
|
||||
} else if (!strcmp(arg, "all")) {
|
||||
kfmon_cmd = "list";
|
||||
} else {
|
||||
NM_RETURN_ERR("invalid argument '%s': if specified, must be either gui or all", arg);
|
||||
NM_ERR_RET(NULL, "invalid argument '%s': if specified, must be either gui or all", arg);
|
||||
}
|
||||
|
||||
// We'll want to retrieve our watch list in there.
|
||||
@@ -115,14 +113,14 @@ NM_GENERATOR_(kfmon) {
|
||||
int status = nm_kfmon_list_request(kfmon_cmd, &list);
|
||||
|
||||
// If there was an error, handle it now.
|
||||
if (status != KFMON_IPC_OK) {
|
||||
return nm_kfmon_error_handler(status, err_out);
|
||||
}
|
||||
if (nm_kfmon_error_handler(status))
|
||||
return NULL; // the error will be passed on
|
||||
|
||||
// Handle an empty listing safely
|
||||
if (list.count == 0) {
|
||||
*sz_out = 0;
|
||||
NM_RETURN_OK(NULL);
|
||||
nm_err_set(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// And now we can start populating an array of nm_menu_item_t :)
|
||||
@@ -146,7 +144,6 @@ NM_GENERATOR_(kfmon) {
|
||||
kfmon_teardown_list(&list);
|
||||
|
||||
*time_in_out = sb.st_mtim;
|
||||
NM_RETURN_OK(items);
|
||||
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return items;
|
||||
}
|
||||
|
||||
48
src/init.c
48
src/init.c
@@ -15,8 +15,6 @@
|
||||
#include "util.h"
|
||||
|
||||
__attribute__((constructor)) void nm_init() {
|
||||
char *err;
|
||||
|
||||
NM_LOG("version: " NM_VERSION);
|
||||
#ifdef NM_UNINSTALL_CONFIGDIR
|
||||
NM_LOG("feature: NM_UNINSTALL_CONFIGDIR: true");
|
||||
@@ -28,9 +26,8 @@ __attribute__((constructor)) void nm_init() {
|
||||
NM_LOG("init: creating failsafe");
|
||||
|
||||
nm_failsafe_t *fs;
|
||||
if (!(fs = nm_failsafe_create(&err)) && err) {
|
||||
NM_LOG("error: could not create failsafe: %s, stopping", err);
|
||||
free(err);
|
||||
if (!(fs = nm_failsafe_create())) {
|
||||
NM_LOG("error: could not create failsafe: %s, stopping", nm_err());
|
||||
goto stop;
|
||||
}
|
||||
|
||||
@@ -55,10 +52,9 @@ __attribute__((constructor)) void nm_init() {
|
||||
|
||||
NM_LOG("init: updating config");
|
||||
|
||||
int rev = nm_global_config_update(&err);
|
||||
if (err) {
|
||||
NM_LOG("init: error parsing config, will show a menu item with the error: %s", err);
|
||||
}
|
||||
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) {
|
||||
@@ -82,9 +78,8 @@ __attribute__((constructor)) void nm_init() {
|
||||
|
||||
NM_LOG("init: hooking libnickel");
|
||||
|
||||
if (nm_menu_hook(libnickel, &err) && err) {
|
||||
NM_LOG("error: could not hook libnickel: %s, stopping", err);
|
||||
free(err);
|
||||
if (nm_menu_hook(libnickel)) {
|
||||
NM_LOG("error: could not hook libnickel: %s, stopping", nm_err());
|
||||
goto stop_fs;
|
||||
}
|
||||
|
||||
@@ -154,31 +149,29 @@ static void nm_global_config_replace(nm_config_t *cfg, const char *err) {
|
||||
NM_LOG("could not allocate memory");
|
||||
}
|
||||
|
||||
int nm_global_config_update(char **err_out) {
|
||||
#define NM_ERR_RET nm_global_menu_config_rev
|
||||
|
||||
char *err;
|
||||
|
||||
int nm_global_config_update() {
|
||||
NM_LOG("global: scanning for config files");
|
||||
bool updated = nm_config_files_update(&nm_global_menu_config_files, &err);
|
||||
if (err) {
|
||||
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_RETURN_ERR("scan for config files: %s", err);
|
||||
NM_ERR_RET(nm_global_menu_config_rev, "scan for config files: %s", err);
|
||||
}
|
||||
NM_LOG("global:%s changes detected", updated ? "" : " no");
|
||||
NM_LOG("global:%s changes detected", state == 0 ? "" : " no");
|
||||
|
||||
if (updated) {
|
||||
if (state == 0) {
|
||||
NM_LOG("global: parsing new config");
|
||||
nm_config_t *cfg = nm_config_parse(nm_global_menu_config_files, &err);
|
||||
if (err) {
|
||||
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_RETURN_ERR("parse config files: %s", err);
|
||||
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");
|
||||
@@ -210,7 +203,6 @@ int nm_global_config_update(char **err_out) {
|
||||
NM_LOG("done replacing items");
|
||||
}
|
||||
|
||||
NM_RETURN_OK(nm_global_menu_config_rev);
|
||||
|
||||
#undef NM_ERR_RET
|
||||
nm_err_set(NULL);
|
||||
return nm_global_menu_config_rev;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ extern "C" {
|
||||
|
||||
// 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).
|
||||
int nm_global_config_update(char **err_out);
|
||||
// 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
|
||||
|
||||
67
src/kfmon.c
67
src/kfmon.c
@@ -394,77 +394,64 @@ int nm_kfmon_list_request(const char *restrict ipc_cmd, kfmon_watch_list_t *list
|
||||
}
|
||||
|
||||
// Giant ladder of fail
|
||||
void *nm_kfmon_error_handler(kfmon_ipc_errno_e status, char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
bool nm_kfmon_error_handler(kfmon_ipc_errno_e status) {
|
||||
switch (status) {
|
||||
// NOTE: Should never be passed a success status code!
|
||||
case KFMON_IPC_OK:
|
||||
NM_RETURN_OK(NULL);
|
||||
return nm_err_set(NULL);
|
||||
// Fail w/ the right log message
|
||||
case KFMON_IPC_ETIMEDOUT:
|
||||
NM_RETURN_ERR("Timed out waiting for KFMon");
|
||||
return nm_err_set("Timed out waiting for KFMon");
|
||||
case KFMON_IPC_EPIPE:
|
||||
NM_RETURN_ERR("KFMon closed the connection");
|
||||
return nm_err_set("KFMon closed the connection");
|
||||
case KFMON_IPC_ENODATA:
|
||||
NM_RETURN_ERR("No more data to read");
|
||||
return nm_err_set("No more data to read");
|
||||
case KFMON_IPC_READ_FAILURE:
|
||||
// NOTE: Let's hope close() won't mangle errno...
|
||||
NM_RETURN_ERR("read: %m");
|
||||
return nm_err_set("read: %m");
|
||||
case KFMON_IPC_SEND_FAILURE:
|
||||
// NOTE: Let's hope close() won't mangle errno...
|
||||
NM_RETURN_ERR("send: %m");
|
||||
return nm_err_set("send: %m");
|
||||
case KFMON_IPC_SOCKET_FAILURE:
|
||||
NM_RETURN_ERR("Failed to create local KFMon IPC socket (socket: %m)");
|
||||
return nm_err_set("Failed to create local KFMon IPC socket (socket: %m)");
|
||||
case KFMON_IPC_CONNECT_FAILURE:
|
||||
NM_RETURN_ERR("KFMon IPC is down (connect: %m)");
|
||||
return nm_err_set("KFMon IPC is down (connect: %m)");
|
||||
case KFMON_IPC_POLL_FAILURE:
|
||||
// NOTE: Let's hope close() won't mangle errno...
|
||||
NM_RETURN_ERR("poll: %m");
|
||||
return nm_err_set("poll: %m");
|
||||
case KFMON_IPC_CALLOC_FAILURE:
|
||||
NM_RETURN_ERR("calloc: %m");
|
||||
return nm_err_set("calloc: %m");
|
||||
case KFMON_IPC_REPLY_READ_FAILURE:
|
||||
// NOTE: Let's hope close() won't mangle errno...
|
||||
NM_RETURN_ERR("Failed to read KFMon's reply (%m)");
|
||||
return nm_err_set("Failed to read KFMon's reply (%m)");
|
||||
case KFMON_IPC_LIST_PARSE_FAILURE:
|
||||
NM_RETURN_ERR("Failed to parse the list of watches (no separator found)");
|
||||
return nm_err_set("Failed to parse the list of watches (no separator found)");
|
||||
case KFMON_IPC_ERR_INVALID_ID:
|
||||
NM_RETURN_ERR("Requested to start an invalid watch index");
|
||||
return nm_err_set("Requested to start an invalid watch index");
|
||||
case KFMON_IPC_ERR_INVALID_NAME:
|
||||
NM_RETURN_ERR("Requested to trigger an invalid watch filename (expected the basename of the image trigger)");
|
||||
return nm_err_set("Requested to trigger an invalid watch filename (expected the basename of the image trigger)");
|
||||
case KFMON_IPC_WARN_ALREADY_RUNNING:
|
||||
NM_RETURN_ERR("Requested watch is already running");
|
||||
return nm_err_set("Requested watch is already running");
|
||||
case KFMON_IPC_WARN_SPAWN_BLOCKED:
|
||||
NM_RETURN_ERR("A spawn blocker is currently running");
|
||||
return nm_err_set("A spawn blocker is currently running");
|
||||
case KFMON_IPC_WARN_SPAWN_INHIBITED:
|
||||
NM_RETURN_ERR("Spawns are currently inhibited");
|
||||
return nm_err_set("Spawns are currently inhibited");
|
||||
case KFMON_IPC_ERR_REALLY_MALFORMED_CMD:
|
||||
NM_RETURN_ERR("KFMon couldn't parse our command");
|
||||
return nm_err_set("KFMon couldn't parse our command");
|
||||
case KFMON_IPC_ERR_MALFORMED_CMD:
|
||||
NM_RETURN_ERR("Bad command syntax");
|
||||
return nm_err_set("Bad command syntax");
|
||||
case KFMON_IPC_ERR_INVALID_CMD:
|
||||
NM_RETURN_ERR("Command wasn't recognized by KFMon");
|
||||
return nm_err_set("Command wasn't recognized by KFMon");
|
||||
case KFMON_IPC_UNKNOWN_REPLY:
|
||||
NM_RETURN_ERR("We couldn't make sense of KFMon's reply");
|
||||
return nm_err_set("We couldn't make sense of KFMon's reply");
|
||||
case KFMON_IPC_EAGAIN:
|
||||
default:
|
||||
// Should never happen
|
||||
NM_RETURN_ERR("Something went wrong");
|
||||
return nm_err_set("Something went wrong");
|
||||
}
|
||||
|
||||
#undef NM_ERR_RET
|
||||
}
|
||||
|
||||
nm_action_result_t *nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err_out) {
|
||||
#define NM_ERR_RET NULL
|
||||
|
||||
switch (status) {
|
||||
case KFMON_IPC_OK:
|
||||
NM_RETURN_OK(nm_action_result_silent());
|
||||
// Fail w/ the right log message
|
||||
default:
|
||||
return nm_kfmon_error_handler(status, err_out);
|
||||
}
|
||||
|
||||
#undef NM_ERR_RET
|
||||
nm_action_result_t *nm_kfmon_return_handler(kfmon_ipc_errno_e status) {
|
||||
if (!nm_kfmon_error_handler(status))
|
||||
return nm_action_result_silent();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
10
src/kfmon.h
10
src/kfmon.h
@@ -4,6 +4,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -73,11 +74,12 @@ void kfmon_teardown_list(kfmon_watch_list_t *list);
|
||||
// Allocate a single new node to the list
|
||||
int kfmon_grow_list(kfmon_watch_list_t *list);
|
||||
|
||||
// Given one of the error codes listed above, return with a formatted error message
|
||||
void *nm_kfmon_error_handler(kfmon_ipc_errno_e status, char **err_out);
|
||||
// If status is success, false is returned. Otherwise, true is returned and
|
||||
// nm_err is set.
|
||||
bool nm_kfmon_error_handler(kfmon_ipc_errno_e status);
|
||||
|
||||
// Given one of the error codes listed above, return properly from an action. Success is silent.
|
||||
nm_action_result_t *nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err_out);
|
||||
// Given one of the error codes listed above, return properly from an action.
|
||||
nm_action_result_t *nm_kfmon_return_handler(kfmon_ipc_errno_e status);
|
||||
|
||||
// Send a simple KFMon IPC request, one where the reply is only used for its diagnostic value.
|
||||
int nm_kfmon_simple_request(const char *restrict ipc_cmd, const char *restrict ipc_arg);
|
||||
|
||||
33
src/menu.cc
33
src/menu.cc
@@ -60,8 +60,7 @@ 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, char **err_out) {
|
||||
#define NM_ERR_RET 1
|
||||
extern "C" int nm_menu_hook(void *libnickel) {
|
||||
//libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
|
||||
//libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb
|
||||
@@ -77,26 +76,25 @@ extern "C" int nm_menu_hook(void *libnickel, char **err_out) {
|
||||
//libnickel 4.6 * _ZN17BoldMenuSeparatorC1EP7QWidget
|
||||
reinterpret_cast<void*&>(BoldMenuSeparator_BoldMenuSeparator) = dlsym(libnickel, "_ZN17BoldMenuSeparatorC1EP7QWidget");
|
||||
|
||||
NM_ASSERT(AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)");
|
||||
NM_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)");
|
||||
NM_ASSERT(ConfirmationDialogFactory_showOKDialog, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(String const&, QString const&)");
|
||||
NM_ASSERT(MainWindowController_sharedInstance, "unsupported firmware: could not find MainWindowController::sharedInstance()");
|
||||
NM_ASSERT(MainWindowController_toast, "unsupported firmware: could not find MainWindowController::toast(QString const&, QString const&, int)");
|
||||
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)");
|
||||
|
||||
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");
|
||||
|
||||
void* nmh = dlsym(RTLD_DEFAULT, "_nm_menu_hook");
|
||||
NM_ASSERT(nmh, "internal error: could not dlsym _nm_menu_hook");
|
||||
NM_CHECK(1, nmh, "internal error: could not dlsym _nm_menu_hook");
|
||||
|
||||
char *err;
|
||||
//libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err);
|
||||
NM_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err);
|
||||
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh);
|
||||
NM_CHECK(1, AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", nm_err());
|
||||
|
||||
NM_RETURN_OK(0);
|
||||
#undef NM_ERR_RET
|
||||
return 0;
|
||||
}
|
||||
|
||||
// AbstractNickelMenuController_createAction_before wraps
|
||||
@@ -141,7 +139,7 @@ void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) {
|
||||
int rev_o = menu->property("nm_config_rev").toInt();
|
||||
|
||||
NM_LOG("checking for config updates (current revision: %d)", rev_o);
|
||||
int rev_n = nm_global_config_update(NULL); // if there was an error it will be returned as a menu item anyways (and updated will be true)
|
||||
int rev_n = nm_global_config_update(); // if there was an error it will be returned as a menu item anyways (and updated will be true)
|
||||
NM_LOG("new revision = %d%s", rev_n, rev_n == rev_o ? "" : " (changed)");
|
||||
|
||||
NM_LOG("checking for existing items added by nm");
|
||||
@@ -202,7 +200,7 @@ void _nm_menu_inject(void *nmc, QMenu *menu, nm_menu_location_t loc, int at) {
|
||||
}
|
||||
|
||||
void nm_menu_item_do(nm_menu_item_t *it) {
|
||||
char *err = NULL;
|
||||
const char *err = NULL;
|
||||
bool success = true;
|
||||
int skip = 0;
|
||||
|
||||
@@ -220,9 +218,9 @@ void nm_menu_item_do(nm_menu_item_t *it) {
|
||||
continue;
|
||||
}
|
||||
|
||||
free(err); // free the previous error if it is not NULL (so the last error can be saved for later)
|
||||
nm_action_result_t *res = cur->act(cur->arg);
|
||||
err = nm_err();
|
||||
|
||||
nm_action_result_t *res = cur->act(cur->arg, &err);
|
||||
if (err == NULL && res && res->type == NM_ACTION_RESULT_TYPE_SKIP) {
|
||||
NM_LOG("...not updating success flag (value=%d) for skip result", success);
|
||||
} else if (!(success = err == NULL)) {
|
||||
@@ -266,7 +264,6 @@ void nm_menu_item_do(nm_menu_item_t *it) {
|
||||
if (err) {
|
||||
NM_LOG("last action returned error %s", err);
|
||||
ConfirmationDialogFactory_showOKDialog(QString::fromUtf8(it->lbl), QString::fromUtf8(err));
|
||||
free(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ typedef struct nm_menu_action_t {
|
||||
char *arg;
|
||||
bool on_success;
|
||||
bool on_failure;
|
||||
nm_action_fn_t act; // can block, must return 0 on success, nonzero with out_err set to the malloc'd error message on error
|
||||
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;
|
||||
|
||||
@@ -29,8 +29,9 @@ typedef struct {
|
||||
} nm_menu_item_t;
|
||||
|
||||
// nm_menu_hook hooks a dlopen'd libnickel handle. It MUST NOT be called more
|
||||
// than once.
|
||||
int nm_menu_hook(void *libnickel, char **err_out);
|
||||
// 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
|
||||
}
|
||||
|
||||
41
src/util.c
Normal file
41
src/util.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static __thread bool nm_err_state = false;
|
||||
static __thread char nm_err_buf[2048] = {0};
|
||||
static __thread char nm_err_buf_tmp[sizeof(nm_err_buf)] = {0}; // in case the format string overlaps
|
||||
|
||||
const char *nm_err() {
|
||||
if (nm_err_state) {
|
||||
nm_err_state = false;
|
||||
return nm_err_buf;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *nm_err_peek() {
|
||||
if (nm_err_state)
|
||||
return nm_err_buf;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool nm_err_set(const char *fmt, ...) {
|
||||
va_list a;
|
||||
if ((nm_err_state = !!fmt)) {
|
||||
va_start(a, fmt);
|
||||
int r = vsnprintf(nm_err_buf_tmp, sizeof(nm_err_buf_tmp), fmt, a);
|
||||
if (r < 0)
|
||||
r = snprintf(nm_err_buf_tmp, sizeof(nm_err_buf_tmp), "error applying format to error string '%s'", fmt);
|
||||
if (r >= (int)(sizeof(nm_err_buf_tmp))) {
|
||||
nm_err_buf_tmp[sizeof(nm_err_buf_tmp) - 2] = '.';
|
||||
nm_err_buf_tmp[sizeof(nm_err_buf_tmp) - 3] = '.';
|
||||
nm_err_buf_tmp[sizeof(nm_err_buf_tmp) - 4] = '.';
|
||||
}
|
||||
memcpy(nm_err_buf, nm_err_buf_tmp, sizeof(nm_err_buf));
|
||||
va_end(a);
|
||||
}
|
||||
return nm_err_state;
|
||||
}
|
||||
66
src/util.h
66
src/util.h
@@ -4,12 +4,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE // asprintf
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
|
||||
@@ -22,7 +18,7 @@ extern "C" {
|
||||
#define NM_LOG_NAME "NickelMenu"
|
||||
#endif
|
||||
|
||||
// Symbol visibility
|
||||
// Symbol visibility (to prevent conflicts when reusing parts of NM)
|
||||
#define NM_PUBLIC __attribute__((visibility("default")))
|
||||
#define NM_PRIVATE __attribute__((visibility("hidden")))
|
||||
|
||||
@@ -37,37 +33,43 @@ inline char *strtrim(char *s) {
|
||||
return a;
|
||||
}
|
||||
|
||||
// A bunch of useful macros to simplify error handling and logging.
|
||||
|
||||
// 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, ...) syslog(LOG_DEBUG, ("(" NM_LOG_NAME ") " fmt " (%s:%d)"), ##__VA_ARGS__, __FILE__, __LINE__)
|
||||
|
||||
// NM_RETURN returns ret, and if ret is NM_ERR_RET and err_out is not NULL, it
|
||||
// writes the formatted error message to *err_out as a malloc'd string. The
|
||||
// arguments may or may not be evaluated more than once.
|
||||
#define NM_RETURN(ret, fmt, ...) _NM_RETURN(0, ret, fmt, ##__VA_ARGS__)
|
||||
#define _NM_RETURN(noerr, ret, fmt, ...) ({ \
|
||||
__typeof__(ret) _ret = (ret); \
|
||||
if (err_out) { \
|
||||
if (!noerr && _ret == NM_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
|
||||
else *err_out = NULL; \
|
||||
} \
|
||||
return _ret; \
|
||||
})
|
||||
// Error handling (thread-safe):
|
||||
|
||||
// NM_ASSERT is like assert, but it writes the formatted error message to
|
||||
// err_out as a malloc'd string, and returns NM_ERR_RET. Cond will always be
|
||||
// evaluated exactly once. The other arguments may or may not be evaluated one
|
||||
// or more times.
|
||||
#define NM_ASSERT(cond, fmt, ...) ({ \
|
||||
if (!(cond)) _NM_RETURN(0, NM_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
|
||||
})
|
||||
// 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();
|
||||
|
||||
// NM_RETURN_ERR is the same as NM_RETURN(NM_ERR_RET, fmt, ...).
|
||||
#define NM_RETURN_ERR(fmt, ...) _NM_RETURN(0, NM_ERR_RET, fmt, ##__VA_ARGS__)
|
||||
// nm_err_peek is like nm_err, but doesn't clear the error state.
|
||||
NM_PRIVATE const char *nm_err_peek();
|
||||
|
||||
// NM_RETURN_OK is the same as NM_RETURN(ret, "").
|
||||
#define NM_RETURN_OK(ret) _NM_RETURN(1, ret, "")
|
||||
// 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)));
|
||||
|
||||
// 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.
|
||||
#define NM_ERR_SET(fmt, ...) \
|
||||
nm_err_set((fmt " (%s:%d)"), ##__VA_ARGS__, __FILE__, __LINE__);
|
||||
|
||||
// NM_ERR_RET is like NM_ERR_SET, but also returns the specified value.
|
||||
#define NM_ERR_RET(ret, fmt, ...) do { \
|
||||
NM_ERR_SET(fmt, ##__VA_ARGS__); \
|
||||
return (ret); \
|
||||
} while (0)
|
||||
|
||||
// NM_CHECK checks a condition and calls nm_err_set then returns the specified
|
||||
// value if the condition is false. Otherwise, nothing happens.
|
||||
#define NM_CHECK(ret, cond, fmt, ...) do { \
|
||||
if (!(cond)) { \
|
||||
nm_err_set((fmt " (check failed: %s)"), ##__VA_ARGS__, #cond); \
|
||||
return (ret); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user