1
0

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:
Patrick Gaskin
2020-07-21 17:47:22 -04:00
committed by GitHub
parent 5a6ae2b462
commit 6329328daf
23 changed files with 403 additions and 432 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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 \

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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))

View File

@@ -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 \

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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
View 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;
}

View File

@@ -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
}