diff --git a/.gitignore b/.gitignore index 7910bbb..f491408 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 89a43a2..24fa8ca 100644 --- a/Makefile +++ b/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 diff --git a/src/action.c b/src/action.c index af388b6..8a6ce33 100644 --- a/src/action.c +++ b/src/action.c @@ -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); diff --git a/src/action.h b/src/action.h index 7d328c3..70d378a 100644 --- a/src/action.h +++ b/src/action.h @@ -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 \ diff --git a/src/action_c.c b/src/action_c.c index 47c0725..0e2e3ef 100644 --- a/src/action_c.c +++ b/src/action_c.c @@ -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); } diff --git a/src/action_cc.cc b/src/action_cc.cc index 99ccd63..c84de43 100644 --- a/src/action_cc.cc +++ b/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(fn_d) = dlsym(RTLD_DEFAULT, sym_d); reinterpret_cast(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(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(Settings_Settings) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Deviceb"); reinterpret_cast(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(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(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(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(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(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(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(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(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(BrowserWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManager14sharedInstanceEv"); reinterpret_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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)); } diff --git a/src/config.c b/src/config.c index 563d823..64de0de 100644 --- a/src/config.c +++ b/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) { diff --git a/src/config.h b/src/config.h index f9463eb..5e3b5a8 100644 --- a/src/config.h +++ b/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. diff --git a/src/dlhook.c b/src/dlhook.c index b3d976e..7f8bf5d 100644 --- a/src/dlhook.c +++ b/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; } diff --git a/src/dlhook.h b/src/dlhook.h index fcd09b9..d166f78 100644 --- a/src/dlhook.h +++ b/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 } diff --git a/src/failsafe.c b/src/failsafe.c index 04d7a1b..1bf8669 100644 --- a/src/failsafe.c +++ b/src/failsafe.c @@ -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) { diff --git a/src/failsafe.h b/src/failsafe.h index 343198a..d5e7040 100644 --- a/src/failsafe.h +++ b/src/failsafe.h @@ -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. diff --git a/src/generator.c b/src/generator.c index 4451c98..d4128fa 100644 --- a/src/generator.c +++ b/src/generator.c @@ -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)) diff --git a/src/generator.h b/src/generator.h index 6a09697..a4120f4 100644 --- a/src/generator.h +++ b/src/generator.h @@ -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 \ diff --git a/src/generator_c.c b/src/generator_c.c index 3b1a28a..0aaee76 100644 --- a/src/generator_c.c +++ b/src/generator_c.c @@ -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; } diff --git a/src/init.c b/src/init.c index b07b03b..4633401 100644 --- a/src/init.c +++ b/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; } diff --git a/src/init.h b/src/init.h index cb371e9..fd923e7 100644 --- a/src/init.h +++ b/src/init.h @@ -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 diff --git a/src/kfmon.c b/src/kfmon.c index 8668584..ae56791 100644 --- a/src/kfmon.c +++ b/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; } diff --git a/src/kfmon.h b/src/kfmon.h index 3501f71..f472921 100644 --- a/src/kfmon.h +++ b/src/kfmon.h @@ -4,6 +4,7 @@ extern "C" { #endif +#include #include #include #include @@ -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); diff --git a/src/menu.cc b/src/menu.cc index ceb2dc3..8d38717 100644 --- a/src/menu.cc +++ b/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(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(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(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err); - NM_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err); + reinterpret_cast(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); } } diff --git a/src/menu.h b/src/menu.h index 9f154f4..cece8a7 100644 --- a/src/menu.h +++ b/src/menu.h @@ -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 } diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..3161ac8 --- /dev/null +++ b/src/util.c @@ -0,0 +1,41 @@ +#define _GNU_SOURCE +#include +#include +#include +#include + +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; +} diff --git a/src/util.h b/src/util.h index c940070..92d6b24 100644 --- a/src/util.h +++ b/src/util.h @@ -4,12 +4,8 @@ extern "C" { #endif -#ifndef _GNU_SOURCE -#define _GNU_SOURCE // asprintf -#endif - #include -#include +#include #include #include @@ -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 }