Implemented kfmon action
Supports launching a kfmon action by its ID or filename. Requires a kfmon version greater than, but not including, v1.3.3. Also includes a hidden POC for getting a complete item list for use later after generators are implemented by @geek1011. Squashed from niluje/kfmon-list-poc, which succeeds niluje/kfmon and niluje/kfmon-v1. commit e6c227b0812f7a8c774de3c48c5121545b6ff39a Author: Patrick Gaskin <patrick@pgaskin.net> Date: Fri May 1 14:52:45 2020 -0400 Cleanup commit f79326d5730da43eaff87572c8c76615b1c61153 Author: Patrick Gaskin <patrick@pgaskin.net> Date: Fri May 1 14:38:08 2020 -0400 Improved kfmon error message commit 8ece03ee867642862a3ed2f948c583125d60d346 Author: NiLuJe <ninuje@gmail.com> Date: Fri May 1 20:14:02 2020 +0200 Eh, we can have our cake, and eat it, too ;). commit e8b25888faaaab891c1129a28ca65959ffe903df Author: NiLuJe <ninuje@gmail.com> Date: Fri May 1 20:09:04 2020 +0200 Drop the "index" mention on INVALID_ID We can use it both with start (id) and trigger (file) on our end. The reply from KFMon is always INVALID_ID, because ultimately a filename lookup points to an index, but internal log messages will make the distinction properly. TL;DR: Keep it vague here, when in doubt, check KFMon's log. commit 351b3d6f3fd906c6a8e848386658408ea8963a79 Author: Patrick Gaskin <patrick@pgaskin.net> Date: Fri May 1 14:00:56 2020 -0400 Added kfmon example to docs commit 3e60e67189fc6068f260ec0afadf9e7f25ae8fa0 Author: NiLuJe <ninuje@gmail.com> Date: Thu Apr 30 22:25:31 2020 +0200 Slightly less unwieldy with a control structure for the list Double pointers get hidden away by a pointer chain via a struct, and it makes helper functions to handle list upkeep easier and clearer. commit 3f21b3050417a076f28218bdaa63a8da8901383a Author: NiLuJe <ninuje@gmail.com> Date: Thu Apr 30 19:01:41 2020 +0200 Minor comment tweaks commit 57d2f58ae49f66c0e99f294e4d8267984ca41d83 Author: NiLuJe <ninuje@gmail.com> Date: Thu Apr 30 18:50:34 2020 +0200 Fix tail pointer updating. This should *actually* do the right thing, and make multi-reads actually behave. Now I just need to trigger one of those to double-check... commit 3b44397fadf7457d6a7a0480939f78bae88f9472 Author: NiLuJe <ninuje@gmail.com> Date: Thu Apr 30 18:35:07 2020 +0200 Investigate multi-reads shenanigans... commit 6dcdd737b19e090979461abe830805cc3e11b5a6 Author: NiLuJe <ninuje@gmail.com> Date: Thu Apr 30 18:29:25 2020 +0200 Simplify list teardown commit a87e8de4c4ea22100ecde8179e8b18d5b0d53a2b Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 19:54:55 2020 +0200 Slightly better comments commit 259087c9f1d303dfd3c9b7ec13eed954c44d10a1 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 19:40:35 2020 +0200 More accurate comment commit c803b1c521408d6d57be1b81cbc79c9bb90febc0 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 19:36:34 2020 +0200 Only allocate as much items as needed (i.e., alloc before storage, not after). That only leaves head to detect properly, because we have to allocate it outside, we can't lose track of it. commit 3e2815bb7a60631767f5c7090cc4d70e4680e3fa Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 19:19:12 2020 +0200 That works much better when I update the right pointer, and not a local copy. commit f85c4c815ea422282187085d4ca08d6b5c0b84f5 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 19:13:14 2020 +0200 Double pointer shenanigans, take one. commit c9932ea79b47d7197f4e48fbc7e4984567859a13 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 03:19:56 2020 +0200 Nnyeah, I'll take this stupid check over risking hitting a NUL put there by strsep when it replaces delimiter bytes... commit 5e41e9e09e37943ee013669f0bbc5c72644a4af1 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 03:04:36 2020 +0200 Botched a newline in the rebase commit ff0c57d805dd2b309f96889458696a0827fb093f Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:44:59 2020 +0200 Forgot to handle the final line ^^. It's a 1 byte string made of a single NUL right behind a LF. commit 7c1b62ce1a3eb364d1ff736867d63e068bbf5c9c Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:41:20 2020 +0200 sigh commit 9e6d0a18cc79a96720fa3abb78c6b3be3a173902 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:21:22 2020 +0200 Duh. That works much better if I don't request pointer-sized reads, but the actual buffer size in bytes, instead. Forgot that when switching to the heap... commit a0e97a19d4a6315583349d03846cec65fc4b86c6 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:14:24 2020 +0200 Plug in the unused arg to appease GCC commit 504ca9487fd2f122bad43bd0e2193adc61ee9952 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:11:38 2020 +0200 Plug that in a debug action commit d9bbdbaea5f9de3f5fd260efd319053913f5e649 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:06:52 2020 +0200 Handle argless IPC commands commit 24021d3f8fb3d77fabd7b595ee4dd945b64f7311 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 02:04:03 2020 +0200 There, DRYer As a bonus, we now handle the extremely unlikely case of a short read on !list commands commit 919467288b4dcbd4564e5da6e8ef6fcd9c7cc132 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 01:57:02 2020 +0200 Simplify, and prepare merging the polling loops commit fa140483c455a286555f7e8afa7f49b6fd8ab801 Author: NiLuJe <ninuje@gmail.com> Date: Wed Apr 29 01:49:57 2020 +0200 Start working on list handling WIP commit, before some refactoring commit 77ecb487fab1301e7dad4a50b4c4a383bfe6a5f9 Author: NiLuJe <ninuje@gmail.com> Date: Tue Apr 28 23:31:55 2020 +0200 Revert "Spike the environment when spawning a command" This reverts commit ea7c617cb8c89fb00b0cb7b95af162038ecb3def. Doesn't actually work (at least w/ startDetached). Not going through /bin/sh -c reparents to init, which is only marginally better, as far as PPID goes. TL;DR: For now, do nothing. commit cec0d58dbe0d2d3e494e085376fc2856e7dfb7b0 Author: NiLuJe <ninuje@gmail.com> Date: Tue Apr 28 22:54:55 2020 +0200 Spike the environment when spawning a command My usual approach is to check $PPID, but /bin/sh -c prevents that here... This is necessary for KOReader, in order to properly handle the 7 billion different ways it can be launched... commit 634f54dbddc0407338c8cacad386a09bc8296c25 Author: NiLuJe <ninuje@gmail.com> Date: Tue Apr 28 22:26:09 2020 +0200 Made connect() EINTR-proof POSIX is fun. commit 0f8fd2dafe2619cc74864240c4a30a2d5dbf89d9 Author: Patrick Gaskin <patrick@pgaskin.net> Date: Tue Apr 28 16:19:15 2020 -0400 Made kfmon headers more consistent with the rest commit 3a997ce6f4299a465eee1299a58e581304cb9a07 Author: NiLuJe <ninuje@gmail.com> Date: Tue Apr 28 00:00:42 2020 +0200 Minor comment tweaks commit 9dccee1f2dc994ecc71dd9f40708437998f600f4 Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 22:59:33 2020 +0200 Resync minor cosmetic tweaks w/ KFMon commit aa0c05b2c38e0061ad3ce1bfbe8ca9c59988413e Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 20:38:27 2020 +0200 Make it build again commit b2f276461fca11a37967bc710526ce065856603c Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 20:31:49 2020 +0200 Resync socket I/O helpers with KFMon Switch to send w/ MSG_NOSIGNAL to handle EPIPE sanely. commit 2270673556a321559a475d7e492c3ea876c4deaf Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 02:45:55 2020 +0200 Forgot to make this static commit 1353558d153129201543713ab72a7e4d4bbf815f Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 02:40:49 2020 +0200 Reword the kfmon section a bit commit 13acd58691bca0d0319674f25f06eb9c365a4ac2 Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 02:30:41 2020 +0200 Double-check that there's someone on the other end of the socket before writing to it. Nickel getting a SIGPIPE would be... not good. :D. commit f3ebf81ca22b587bedab81fb77ea905b25a119c6 Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 02:14:22 2020 +0200 Resync helpers w/ KFMon commit f1222e99eff7765f52aa9fb4be851622cedde008 Author: NiLuJe <ninuje@gmail.com> Date: Mon Apr 27 01:29:39 2020 +0200 Tighter scope commit 0efa76d9c77f7b44a6da53a276b3e21316c0a49c Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 22:22:11 2020 +0200 Stupid bug is stupid... Actually timeout for real, instead of resetting the retry counter like an idiot. commit fb52fa9b7d92a6a85a1134b4ae56cfca7f7448f0 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 17:47:51 2020 +0200 Use restricted pointers We know there aren't any aliasing issues commit 9df1eabe79636e0b2d0f922adb86569ddfda3810 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 06:14:17 2020 +0200 Better variable name, bis. commit 40f0b3e32f8fa14e99aa7415cb682c505106115a Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 06:12:53 2020 +0200 Better variable name commit e5ae5ee8568b3a8b345bc3ab2a9a2261bd49ca83 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 06:09:29 2020 +0200 Expand on that comment Nothing will horribly blow up, KFMon already truncates on its end if need be. commit 50e774676ef788ee6338620890d6d66dff47d221 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 06:08:39 2020 +0200 Tighter scope, smaller buffer KFMon's own config buffer for the *full* path is only 128 bytes. commit 9409b2c49ae7acdb9f329e662438c88ce5e69fce Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:43:20 2020 +0200 Comment typo commit 0a8d16eafd83593c223bd668f48b3a9f52f2b937 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:42:03 2020 +0200 Detail a bit more why we poll for a reply Spoiler: Because networking is networking, even over a local socket ;p. commit 139195dee1f5e7760ab7a82f7d8bf8750971a746 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:38:29 2020 +0200 Fix a sign-compare warning commit 8ded488840c11c83a696e0f6711e5e82e6ab1959 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:37:45 2020 +0200 Moved the polling in its own chunk, too. commit e8264e2cebcb26b1b57c3bd4a027b043c808d2ab Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:27:04 2020 +0200 Start breaking down nm_kfmon_simple_request in small reusable pieces commit 592fb6997aa48c579460de9ee66e99039f09db1f Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:16:34 2020 +0200 Minor simplification commit 00f52c9a5c3f8945230a187d10e3dc42bc1079b3 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 04:13:08 2020 +0200 Minor refactor of error handling in kfmon actions Should allow to break it down in smaller pieces to ease list handling later... commit e2af99fa6d62a485d5568d7faf9e5a627b6ed484 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 02:43:12 2020 +0200 Gah. commit 0302989693be02af737ef32921fbb4b45127fe40 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 02:42:33 2020 +0200 Update comments commit 4b4628b3bbab2edfe9ed80ef15aeffa67beba713 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 02:39:20 2020 +0200 Switch the kfmon action to filename lookups, as they're stable ID lookups are still available via a kfmon_id action, which is left undocument to avoid confusion. commit 464b376ffb53d20291ffefbb93a90cbeaa94420d Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 00:46:11 2020 +0200 With a decent optimization level while we're there ;). commit 1e55f6ea737bd491025ae9239787d7fd8211da9d Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 00:38:23 2020 +0200 It's actually for ppoll(), which I don't use. Still, we're on Linux, _GNU_SOURCE makes sense, I may be relying on GNUisms because I usually always have _GNU_SOURCE set. commit c3b1f491b5dc3d6b7854b160ab7de3f24cc03bc1 Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 00:35:51 2020 +0200 Really make -Werror happy commit 12b0d17c20d30948cc5c0a58e1671db96de63f7c Author: NiLuJe <ninuje@gmail.com> Date: Sun Apr 26 00:07:13 2020 +0200 Make -Werror happy ;). commit 03f86d0b802c1409291490a51b981620eba45f87 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 23:53:18 2020 +0200 Some more refactoring commit 3b63b054f41dce0b8f43b596b6fc2885434065bb Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 22:50:37 2020 +0200 Make KFMon-specific stuff private (i.e., static) commit e189d84815de6adc1f60e654cf873c9328ea2380 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 22:39:34 2020 +0200 Make gitignore tweaks survive a regen c.f., https://stackoverflow.com/questions/5873025 commit 6a01d0e920cbc15c7bab7dc0ddae3a6d19b26acd Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 20:35:57 2020 +0200 Refresh gitignore commit 3858b0ab846a4bd6415096c962a36bd226d708f9 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 20:34:54 2020 +0200 We don't use that commit e8711d785ea1f442184cfcca976bc8a1ac0a8eb9 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 20:28:04 2020 +0200 Parse KFMon's reply for nicer feedback on failures commit a2dd5994646a2ce8e36203cabe28ae6c8c665056 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 20:00:48 2020 +0200 Initial implementation of the KFMon IPC start command Based on kfmon-ipc, hopefully haven't mangled anything too much. (Untested) commit 2e99cbe00c20d160e71e05a7c1d9c8c66b554235 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 18:02:27 2020 +0200 Switch to GNU syntax for macros without duplication of side-effects. c.f., https://gcc.gnu.org/onlinedocs/cpp/Duplication-of-Side-Effects.html#Duplication-of-Side-Effects (Same code is generated, but at least this way Clang no longers flood my IDE with red squiggly lines across eveyr other line ;p). commit 87b9d6dc95b0539ff5942d0414d26ff7a69e61af Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 18:01:45 2020 +0200 Add KDevelop project files to gitignore commit 456073e46216427f9fbd68a9ab0fc676e0febf76 Author: NiLuJe <ninuje@gmail.com> Date: Sat Apr 25 18:01:07 2020 +0200 Update doc for first iteration of kfmon integration (Still TODO ;p).
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,4 +1,11 @@
|
|||||||
# make gitignore
|
# make gitignore
|
||||||
|
|
||||||
|
# KDevelop
|
||||||
|
.kdev4/
|
||||||
|
NickelMenu.kdev4
|
||||||
|
.kateconfig
|
||||||
|
|
||||||
|
# Build artefacts
|
||||||
/KoboRoot.tgz
|
/KoboRoot.tgz
|
||||||
/src/libnm.so
|
/src/libnm.so
|
||||||
|
|
||||||
@@ -9,6 +16,7 @@
|
|||||||
/src/failsafe.o
|
/src/failsafe.o
|
||||||
/src/init.o
|
/src/init.o
|
||||||
/src/config.o
|
/src/config.o
|
||||||
|
/src/kfmon.o
|
||||||
/src/action.o
|
/src/action.o
|
||||||
/src/dlhook.o
|
/src/dlhook.o
|
||||||
|
|
||||||
|
|||||||
16
Makefile
16
Makefile
@@ -41,13 +41,25 @@ override CXXFLAGS += -std=gnu++11 -Wall -Wextra -Werror
|
|||||||
override LDFLAGS += -Wl,--no-undefined -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib
|
override LDFLAGS += -Wl,--no-undefined -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
define GITIGNORE_HEAD
|
||||||
|
# make gitignore
|
||||||
|
|
||||||
|
# KDevelop
|
||||||
|
.kdev4/
|
||||||
|
NickelMenu.kdev4
|
||||||
|
.kateconfig
|
||||||
|
|
||||||
|
# Build artefacts
|
||||||
|
endef
|
||||||
|
export GITIGNORE_HEAD
|
||||||
|
|
||||||
all: src/libnm.so
|
all: src/libnm.so
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(GENERATED)
|
rm -f $(GENERATED)
|
||||||
|
|
||||||
gitignore:
|
gitignore:
|
||||||
echo '# make gitignore' > .gitignore
|
echo "$${GITIGNORE_HEAD}" > .gitignore
|
||||||
echo '$(GENERATED)' | \
|
echo '$(GENERATED)' | \
|
||||||
sed 's/ /\n/g' | \
|
sed 's/ /\n/g' | \
|
||||||
sed 's/^./\/&/' >> .gitignore
|
sed 's/^./\/&/' >> .gitignore
|
||||||
@@ -65,7 +77,7 @@ override GENERATED += KoboRoot.tgz
|
|||||||
src/libnm.so: override CFLAGS += $(PTHREAD_CFLAGS) -fPIC
|
src/libnm.so: override CFLAGS += $(PTHREAD_CFLAGS) -fPIC
|
||||||
src/libnm.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fPIC
|
src/libnm.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fPIC
|
||||||
src/libnm.so: override LDFLAGS += $(PTHREAD_LIBS) $(QT5CORE_LIBS) $(QT5WIDGETS_LIBS) -ldl -Wl,-soname,libnm.so
|
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/action.o src/action_c.o src/action_cc.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
|
||||||
|
|
||||||
override LIBRARIES += src/libnm.so
|
override LIBRARIES += src/libnm.so
|
||||||
override MOCS += src/qtplugin.moc
|
override MOCS += src/qtplugin.moc
|
||||||
|
|||||||
9
res/doc
9
res/doc
@@ -23,7 +23,7 @@
|
|||||||
# dbg_error - always returns an error (for testing)
|
# dbg_error - always returns an error (for testing)
|
||||||
# dbg_msg - shows a message (for testing)
|
# dbg_msg - shows a message (for testing)
|
||||||
# dbg_toast - shows a toast (for testing)
|
# dbg_toast - shows a toast (for testing)
|
||||||
# kfmon - triggers a kfmon action (TODO)
|
# kfmon - triggers a kfmon action
|
||||||
# nickel_setting - toggles a boolean setting
|
# nickel_setting - toggles a boolean setting
|
||||||
# nickel_extras - opens one of the beta features
|
# nickel_extras - opens one of the beta features
|
||||||
# nickel_misc - other stuff which isn't significant enough for its own category
|
# nickel_misc - other stuff which isn't significant enough for its own category
|
||||||
@@ -34,7 +34,9 @@
|
|||||||
# dbg_error - the error message
|
# dbg_error - the error message
|
||||||
# dbg_msg - the message
|
# dbg_msg - the message
|
||||||
# dbg_toast - the message
|
# dbg_toast - the message
|
||||||
# kfmon - TODO
|
# kfmon - the filename of the KFMon watched item to launch.
|
||||||
|
# This is actually the basename of the watch's filename as specified in its KFMon config.
|
||||||
|
# You can also check the output of the 'list' command via the kfmon-ipc tool.
|
||||||
# nickel_setting - one of:
|
# nickel_setting - one of:
|
||||||
# invert - toggles FeatureSettings.InvertScreen (all versions)
|
# invert - toggles FeatureSettings.InvertScreen (all versions)
|
||||||
# lockscreen - toggles PowerSettings.UnlockEnabled (4.12.12111+)
|
# lockscreen - toggles PowerSettings.UnlockEnabled (4.12.12111+)
|
||||||
@@ -66,6 +68,7 @@
|
|||||||
# menu_item :main :Do Nothing :cmd_spawn :sleep 60
|
# menu_item :main :Do Nothing :cmd_spawn :sleep 60
|
||||||
# menu_item :main :Kernel Version :cmd_output :500:uname -a
|
# menu_item :main :Kernel Version :cmd_output :500:uname -a
|
||||||
# menu_item :main :Sketch Pad :nickel_extras :Sketch Pad
|
# menu_item :main :Sketch Pad :nickel_extras :Sketch Pad
|
||||||
|
# menu_item :main :Plato :kfmon :plato.png
|
||||||
# menu_item :reader :Invert Screen :nickel_setting :invert
|
# menu_item :reader :Invert Screen :nickel_setting :invert
|
||||||
#
|
#
|
||||||
# You will need to reboot to see any changes.
|
# You will need to reboot to see any changes.
|
||||||
@@ -78,4 +81,4 @@
|
|||||||
# To uninstall NickelMenu, create a file named KOBOeReader/.adds/nm/uninstall,
|
# To uninstall NickelMenu, create a file named KOBOeReader/.adds/nm/uninstall,
|
||||||
# or manually uninstall it by deleting libnm.so. You can also uninstall it by
|
# or manually uninstall it by deleting libnm.so. You can also uninstall it by
|
||||||
# triggering the failsafe mechanism by turning your Kobo off within 20 seconds
|
# triggering the failsafe mechanism by turning your Kobo off within 20 seconds
|
||||||
# of turning it on.
|
# of turning it on.
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ void nm_action_result_free(nm_action_result_t *res);
|
|||||||
X(dbg_msg) \
|
X(dbg_msg) \
|
||||||
X(dbg_toast) \
|
X(dbg_toast) \
|
||||||
X(kfmon) \
|
X(kfmon) \
|
||||||
|
X(kfmon_id) \
|
||||||
|
X(kfmon_list) \
|
||||||
X(nickel_setting) \
|
X(nickel_setting) \
|
||||||
X(nickel_extras) \
|
X(nickel_extras) \
|
||||||
X(nickel_misc) \
|
X(nickel_misc) \
|
||||||
@@ -49,4 +51,4 @@ NM_ACTIONS
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#define _GNU_SOURCE // asprintf
|
#define _GNU_SOURCE // asprintf
|
||||||
|
|
||||||
#include "action.h"
|
#include "action.h"
|
||||||
|
#include "kfmon.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
NM_ACTION_(dbg_syslog) {
|
NM_ACTION_(dbg_syslog) {
|
||||||
@@ -28,8 +29,27 @@ NM_ACTION_(dbg_toast) {
|
|||||||
#undef NM_ERR_RET
|
#undef NM_ERR_RET
|
||||||
}
|
}
|
||||||
|
|
||||||
NM_ACTION_(kfmon) {
|
NM_ACTION_(kfmon_id) {
|
||||||
#define NM_ERR_RET NULL
|
// Start by watch ID (simpler, but IDs may not be stable across a single power cycle, given severe KFMon config shuffling)
|
||||||
NM_RETURN_ERR("not implemented yet (arg=%s)", arg); // TODO
|
int status = nm_kfmon_simple_request("start", arg);
|
||||||
#undef NM_ERR_RET
|
return nm_kfmon_return_handler(status, err_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
NM_ACTION_(kfmon) {
|
||||||
|
// Trigger a watch, given its trigger basename. Stable runtime lookup done by 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) {
|
||||||
|
status = KFMON_IPC_ERR_INVALID_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nm_kfmon_return_handler(status, err_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoC
|
||||||
|
NM_ACTION_(kfmon_list) {
|
||||||
|
// Request a list from KFMon, parses it, and send the results to syslog
|
||||||
|
int status = nm_kfmon_list_request(arg);
|
||||||
|
return nm_kfmon_return_handler(status, err_out);
|
||||||
}
|
}
|
||||||
|
|||||||
482
src/kfmon.c
Normal file
482
src/kfmon.c
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "kfmon_helpers.h"
|
||||||
|
#include "kfmon.h"
|
||||||
|
|
||||||
|
// Free all resources allocated by a list and its nodes
|
||||||
|
static void teardown_list(kfmon_watch_list_t* list) {
|
||||||
|
kfmon_watch_node_t* node = list->head;
|
||||||
|
while (node) {
|
||||||
|
NM_LOG("Freeing node at %p", node);
|
||||||
|
kfmon_watch_node_t* p = node->next;
|
||||||
|
free(node->watch.filename);
|
||||||
|
free(node->watch.label);
|
||||||
|
free(node);
|
||||||
|
node = p;
|
||||||
|
}
|
||||||
|
// Don't leave dangling pointers
|
||||||
|
list->head = NULL;
|
||||||
|
list->tail = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a single new node to the list
|
||||||
|
static int grow_list(kfmon_watch_list_t* list) {
|
||||||
|
kfmon_watch_node_t* prev = list->tail;
|
||||||
|
kfmon_watch_node_t* node = calloc(1, sizeof(*node));
|
||||||
|
if (!node) {
|
||||||
|
return KFMON_IPC_CALLOC_FAILURE;
|
||||||
|
}
|
||||||
|
list->count++;
|
||||||
|
|
||||||
|
// Update the head if this is the first node
|
||||||
|
if (!list->head) {
|
||||||
|
list->head = node;
|
||||||
|
}
|
||||||
|
// Update the tail pointer
|
||||||
|
list->tail = node;
|
||||||
|
// If there was a previous node, link the two together
|
||||||
|
if (prev) {
|
||||||
|
prev->next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle replies from the IPC socket
|
||||||
|
static int handle_reply(int data_fd, void *data __attribute__((unused))) {
|
||||||
|
// Eh, recycle PIPE_BUF, it should be more than enough for our needs.
|
||||||
|
char buf[PIPE_BUF] = { 0 };
|
||||||
|
|
||||||
|
// We don't actually know the size of the reply, so, best effort here.
|
||||||
|
ssize_t len = xread(data_fd, buf, sizeof(buf));
|
||||||
|
if (len < 0) {
|
||||||
|
// Only actual failures are left, xread handles the rest
|
||||||
|
return KFMON_IPC_REPLY_READ_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's actually nothing to read (EoF), abort.
|
||||||
|
if (len == 0) {
|
||||||
|
return KFMON_IPC_ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the reply for failures
|
||||||
|
if (strncmp(buf, "ERR_INVALID_ID", 14) == 0) {
|
||||||
|
return KFMON_IPC_ERR_INVALID_ID;
|
||||||
|
} else if (strncmp(buf, "WARN_ALREADY_RUNNING", 20) == 0) {
|
||||||
|
return KFMON_IPC_WARN_ALREADY_RUNNING;
|
||||||
|
} else if (strncmp(buf, "WARN_SPAWN_BLOCKED", 18) == 0) {
|
||||||
|
return KFMON_IPC_WARN_SPAWN_BLOCKED;
|
||||||
|
} else if (strncmp(buf, "WARN_SPAWN_INHIBITED", 20) == 0) {
|
||||||
|
return KFMON_IPC_WARN_SPAWN_INHIBITED;
|
||||||
|
} else if (strncmp(buf, "ERR_REALLY_MALFORMED_CMD", 24) == 0) {
|
||||||
|
return KFMON_IPC_ERR_REALLY_MALFORMED_CMD;
|
||||||
|
} else if (strncmp(buf, "ERR_MALFORMED_CMD", 17) == 0) {
|
||||||
|
return KFMON_IPC_ERR_MALFORMED_CMD;
|
||||||
|
} else if (strncmp(buf, "ERR_INVALID_CMD", 15) == 0) {
|
||||||
|
return KFMON_IPC_ERR_INVALID_CMD;
|
||||||
|
} else if (strncmp(buf, "OK", 2) == 0) {
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
} else {
|
||||||
|
return KFMON_IPC_UNKNOWN_REPLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're not done until we've got a reply we're satisfied with...
|
||||||
|
return KFMON_IPC_EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle replies from a 'list' command
|
||||||
|
static int handle_list_reply(int data_fd, void *data) {
|
||||||
|
// Can't do it on the stack because of strsep
|
||||||
|
char *buf = NULL;
|
||||||
|
buf = calloc(PIPE_BUF, sizeof(*buf));
|
||||||
|
if (!buf) {
|
||||||
|
return KFMON_IPC_CALLOC_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Until proven otherwise...
|
||||||
|
int status = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
// We don't actually know the size of the reply, so, best effort here.
|
||||||
|
ssize_t len = xread(data_fd, buf, PIPE_BUF);
|
||||||
|
if (len < 0) {
|
||||||
|
// Only actual failures are left, xread handles the rest
|
||||||
|
status = KFMON_IPC_REPLY_READ_FAILURE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's actually nothing to read (EoF), abort.
|
||||||
|
if (len == 0) {
|
||||||
|
status = KFMON_IPC_ENODATA;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only valid reply for list is... a list ;).
|
||||||
|
if (strncmp(buf, "ERR_INVALID_CMD", 15) == 0) {
|
||||||
|
status = KFMON_IPC_ERR_INVALID_CMD;
|
||||||
|
goto cleanup;
|
||||||
|
} else if ((strncmp(buf, "WARN_", 5) == 0) ||
|
||||||
|
(strncmp(buf, "ERR_", 4) == 0) ||
|
||||||
|
(strncmp(buf, "OK", 2) == 0)) {
|
||||||
|
status = KFMON_IPC_UNKNOWN_REPLY;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Replies may be split across multiple reads (and as such, multiple handle_list_reply calls).
|
||||||
|
// So the only way we can be sure that we're done (short of timing out after a while of no POLLIN revents,
|
||||||
|
// which would be stupid), is to check that the final byte we've just read is a NUL,
|
||||||
|
// as that's how KFMon terminates a list.
|
||||||
|
bool eot = false;
|
||||||
|
// NOTE: The parsing code later does its own take on this to detect the final line,
|
||||||
|
// but this one should be authoritative, as strsep modifies the data.
|
||||||
|
if (buf[len - 1] == '\0') {
|
||||||
|
eot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE to self: syslog strips LF, you dummy.
|
||||||
|
NM_LOG("<<< Got a %zd bytes reply:\n%.*s", len, len, buf);
|
||||||
|
// Now that we're sure we didn't get a wonky reply from an unrelated command, parse the list
|
||||||
|
// NOTE: Format is:
|
||||||
|
// id:filename:label or id:filename for watches without a label
|
||||||
|
// We don't care about id, as it potentially won't be stable across the full powercycle,
|
||||||
|
// filename is what we pass verbatim to a kfmon action
|
||||||
|
// label is our action's lbl (use filename if NULL)
|
||||||
|
char *p = buf;
|
||||||
|
char *line = NULL;
|
||||||
|
// Break the reply line by line
|
||||||
|
while ((line = strsep(&p, "\n")) != NULL) {
|
||||||
|
// Then parse each line
|
||||||
|
NM_LOG("Parsing line: %s\n", line);
|
||||||
|
// If it's the final line, its only content is a single NUL
|
||||||
|
if (*line == '\0') {
|
||||||
|
// NOTE: This might also simply be the end of a single-line read,
|
||||||
|
// in which case the NUL is thanks to calloc...
|
||||||
|
NM_LOG("Caught an empty line! EoT? %d", eot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// NOTE: Simple syslog logging for now
|
||||||
|
char *watch_idx = strsep(&line, ":");
|
||||||
|
if (!watch_idx) {
|
||||||
|
status = KFMON_IPC_LIST_PARSE_FAILURE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
NM_LOG("watch index: %s", watch_idx);
|
||||||
|
char *filename = strsep(&line, ":");
|
||||||
|
if (!filename) {
|
||||||
|
status = KFMON_IPC_LIST_PARSE_FAILURE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
NM_LOG("filename: '%s'", filename);
|
||||||
|
// Final separator is optional, if it's not there, there's no label, use the filename instead.
|
||||||
|
char *label = strsep(&line, ":");
|
||||||
|
if (label) {
|
||||||
|
NM_LOG("label: '%s'", label);
|
||||||
|
} else {
|
||||||
|
NM_LOG("label -> filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store that at the tail of the list
|
||||||
|
kfmon_watch_list_t* list = (kfmon_watch_list_t*) data;
|
||||||
|
// Make room for a new node
|
||||||
|
if (grow_list(list) != EXIT_SUCCESS) {
|
||||||
|
status = KFMON_IPC_CALLOC_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Use it
|
||||||
|
kfmon_watch_node_t* node = list->tail;
|
||||||
|
NM_LOG("New node at %p", node);
|
||||||
|
|
||||||
|
node->watch.idx = (uint8_t) strtoul(watch_idx, NULL, 10);
|
||||||
|
node->watch.filename = strdup(filename);
|
||||||
|
node->watch.label = label ? strdup(label) : strdup(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we really done?
|
||||||
|
status = eot ? EXIT_SUCCESS : KFMON_IPC_EAGAIN;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
free(buf);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to KFMon's IPC socket. Returns error code, store data fd by ref.
|
||||||
|
static int connect_to_kfmon_socket(int *restrict data_fd) {
|
||||||
|
// Setup the local socket
|
||||||
|
if ((*data_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) == -1) {
|
||||||
|
return KFMON_IPC_SOCKET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un sock_name = { 0 };
|
||||||
|
sock_name.sun_family = AF_UNIX;
|
||||||
|
strncpy(sock_name.sun_path, KFMON_IPC_SOCKET, sizeof(sock_name.sun_path) - 1);
|
||||||
|
|
||||||
|
// Connect to IPC socket, retrying safely on EINTR (c.f., http://www.madore.org/~david/computers/connect-intr.html)
|
||||||
|
while (connect(*data_fd, (const struct sockaddr*) &sock_name, sizeof(sock_name)) == -1 && errno != EISCONN) {
|
||||||
|
if (errno != EINTR) {
|
||||||
|
return KFMON_IPC_CONNECT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wheee!
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a packet to KFMon over the wire (payload *MUST* be NUL-terminated to avoid truncation, and len *MUST* include that NUL).
|
||||||
|
static int send_packet(int data_fd, const char *restrict payload, size_t len) {
|
||||||
|
// Send it (w/ a NUL)
|
||||||
|
if (send_in_full(data_fd, payload, len) < 0) {
|
||||||
|
// Only actual failures are left
|
||||||
|
if (errno == EPIPE) {
|
||||||
|
return KFMON_IPC_EPIPE;
|
||||||
|
} else {
|
||||||
|
return KFMON_IPC_SEND_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wheee!
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the requested IPC command:arg pair (or command alone if arg is NULL)
|
||||||
|
static int send_ipc_command(int data_fd, const char *restrict ipc_cmd, const char *restrict ipc_arg) {
|
||||||
|
char buf[256] = { 0 };
|
||||||
|
int packet_len = 0;
|
||||||
|
// Somme commands don't require an arg
|
||||||
|
if (ipc_arg) {
|
||||||
|
packet_len = snprintf(buf, sizeof(buf), "%s:%s", ipc_cmd, ipc_arg);
|
||||||
|
} else {
|
||||||
|
packet_len = snprintf(buf, sizeof(buf), "%s", ipc_cmd);
|
||||||
|
}
|
||||||
|
// Send it (w/ a NUL)
|
||||||
|
return send_packet(data_fd, buf, (size_t) (packet_len + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll the IPC socket for potentially *multiple* replies to a single command, timeout after attempts * timeout (ms)
|
||||||
|
static int wait_for_replies(int data_fd, int timeout, size_t attempts, ipc_handler_t reply_handler, void **data) {
|
||||||
|
int status = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
// Data socket
|
||||||
|
pfd.fd = data_fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
|
||||||
|
// Here goes... We'll wait for <attempts> windows of <timeout>ms
|
||||||
|
size_t retry = 0U;
|
||||||
|
while (1) {
|
||||||
|
int poll_num = poll(&pfd, 1, timeout);
|
||||||
|
if (poll_num == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return KFMON_IPC_POLL_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_num > 0) {
|
||||||
|
if (pfd.revents & POLLIN) {
|
||||||
|
// There was a reply from the socket
|
||||||
|
int reply = reply_handler(data_fd, data);
|
||||||
|
if (reply != EXIT_SUCCESS) {
|
||||||
|
// If the remote closed the connection, we get POLLIN|POLLHUP w/ EoF ;).
|
||||||
|
if (pfd.revents & POLLHUP) {
|
||||||
|
// Flag that as an error
|
||||||
|
status = KFMON_IPC_EPIPE;
|
||||||
|
} else {
|
||||||
|
if (reply == KFMON_IPC_EAGAIN) {
|
||||||
|
// We're expecting more stuff to read, keep going.
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// Something went wrong when handling the reply, pass the error as-is
|
||||||
|
status = reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We're obviously done if something went wrong.
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// We break on success, too, as we only need to send a single command.
|
||||||
|
status = EXIT_SUCCESS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote closed the connection
|
||||||
|
if (pfd.revents & POLLHUP) {
|
||||||
|
// Flag that as an error
|
||||||
|
status = KFMON_IPC_EPIPE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_num == 0) {
|
||||||
|
// Timed out, increase the retry counter
|
||||||
|
retry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the axe after the final attempt
|
||||||
|
if (retry >= attempts) {
|
||||||
|
status = KFMON_IPC_ETIMEDOUT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a simple KFMon IPC request
|
||||||
|
int nm_kfmon_simple_request(const char *restrict ipc_cmd, const char *restrict ipc_arg) {
|
||||||
|
// Assume everything's peachy until shit happens...
|
||||||
|
int status = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
int data_fd = -1;
|
||||||
|
// Attempt to connect to KFMon...
|
||||||
|
// As long as KFMon is up, has very little chance to fail, even if the connection backlog is full.
|
||||||
|
status = connect_to_kfmon_socket(&data_fd);
|
||||||
|
// If it failed, return early
|
||||||
|
if (status != EXIT_SUCCESS) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send the specified command in full over the wire
|
||||||
|
status = send_ipc_command(data_fd, ipc_cmd, ipc_arg);
|
||||||
|
// If it failed, return early, after closing the socket
|
||||||
|
if (status != EXIT_SUCCESS) {
|
||||||
|
close(data_fd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll be polling the socket for a reply, this'll make things neater, and allows us to abort on timeout,
|
||||||
|
// in the unlikely event there's already an IPC session being handled by KFMon,
|
||||||
|
// in which case the reply would be delayed by an undeterminate amount of time (i.e., until KFMon gets to it).
|
||||||
|
// Here, we'll want to timeout after 2s
|
||||||
|
ipc_handler_t handler = &handle_reply;
|
||||||
|
status = wait_for_replies(data_fd, 500, 4, handler, NULL);
|
||||||
|
// NOTE: We happen to be done with the connection right now.
|
||||||
|
// But if we still needed it, KFMON_IPC_POLL_FAILURE would warrant an early abort w/ a forced close().
|
||||||
|
|
||||||
|
// Bye now!
|
||||||
|
close(data_fd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoC handling of a list request
|
||||||
|
int nm_kfmon_list_request(const char *restrict foo __attribute__((unused))) {
|
||||||
|
// Assume everything's peachy until shit happens...
|
||||||
|
int status = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
int data_fd = -1;
|
||||||
|
// Attempt to connect to KFMon...
|
||||||
|
// As long as KFMon is up, has very little chance to fail, even if the connection backlog is full.
|
||||||
|
status = connect_to_kfmon_socket(&data_fd);
|
||||||
|
// If it failed, return early
|
||||||
|
if (status != EXIT_SUCCESS) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send the specified command in full over the wire
|
||||||
|
status = send_ipc_command(data_fd, "list", NULL); // FIXME: switch to gui-list
|
||||||
|
// If it failed, return early, after closing the socket
|
||||||
|
if (status != EXIT_SUCCESS) {
|
||||||
|
close(data_fd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll want to retrieve our watch list in there.
|
||||||
|
kfmon_watch_list_t list = { 0 };
|
||||||
|
|
||||||
|
// We'll be polling the socket for a reply, this'll make things neater, and allows us to abort on timeout,
|
||||||
|
// in the unlikely event there's already an IPC session being handled by KFMon,
|
||||||
|
// in which case the reply would be delayed by an undeterminate amount of time (i.e., until KFMon gets to it).
|
||||||
|
// Here, we'll want to timeout after 2s
|
||||||
|
ipc_handler_t handler = &handle_list_reply;
|
||||||
|
status = wait_for_replies(data_fd, 500, 4, handler, (void *) &list);
|
||||||
|
// NOTE: We happen to be done with the connection right now.
|
||||||
|
// But if we still needed it, KFMON_IPC_POLL_FAILURE would warrant an early abort w/ a forced close().
|
||||||
|
|
||||||
|
// Walk the list
|
||||||
|
NM_LOG("List has %zu nodes", list.count);
|
||||||
|
NM_LOG("Head is at %p", list.head);
|
||||||
|
NM_LOG("Tail is at %p", list.tail);
|
||||||
|
kfmon_watch_node_t* node = list.head;
|
||||||
|
while (node) {
|
||||||
|
NM_LOG("Dumping node at %p", node);
|
||||||
|
NM_LOG("idx: %hhu // filename: %s // label: %s", node->watch.idx, node->watch.filename, node->watch.label);
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy it
|
||||||
|
teardown_list(&list);
|
||||||
|
|
||||||
|
// Bye now!
|
||||||
|
close(data_fd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Giant ladder of fail
|
||||||
|
nm_action_result_t* nm_kfmon_return_handler(int status, char **err_out) {
|
||||||
|
#define NM_ERR_RET NULL
|
||||||
|
|
||||||
|
if (status != EXIT_SUCCESS) {
|
||||||
|
// Fail w/ the right log message
|
||||||
|
if (status == KFMON_IPC_ETIMEDOUT) {
|
||||||
|
NM_RETURN_ERR("Timed out waiting for KFMon");
|
||||||
|
} else if (status == KFMON_IPC_EPIPE) {
|
||||||
|
NM_RETURN_ERR("KFMon closed the connection");
|
||||||
|
} else if (status == KFMON_IPC_ENODATA) {
|
||||||
|
NM_RETURN_ERR("No more data to read");
|
||||||
|
} else if (status == KFMON_IPC_READ_FAILURE) {
|
||||||
|
// NOTE: Let's hope close() won't mangle errno...
|
||||||
|
NM_RETURN_ERR("read: %m");
|
||||||
|
} else if (status == KFMON_IPC_SEND_FAILURE) {
|
||||||
|
// NOTE: Let's hope close() won't mangle errno...
|
||||||
|
NM_RETURN_ERR("send: %m");
|
||||||
|
} else if (status == KFMON_IPC_SOCKET_FAILURE) {
|
||||||
|
NM_RETURN_ERR("Failed to create local KFMon IPC socket (socket: %m)");
|
||||||
|
} else if (status == KFMON_IPC_CONNECT_FAILURE) {
|
||||||
|
NM_RETURN_ERR("KFMon IPC is down (connect: %m)");
|
||||||
|
} else if (status == KFMON_IPC_POLL_FAILURE) {
|
||||||
|
// NOTE: Let's hope close() won't mangle errno...
|
||||||
|
NM_RETURN_ERR("poll: %m");
|
||||||
|
} else if (status == KFMON_IPC_CALLOC_FAILURE) {
|
||||||
|
NM_RETURN_ERR("calloc: %m");
|
||||||
|
} else if (status == KFMON_IPC_REPLY_READ_FAILURE) {
|
||||||
|
// NOTE: Let's hope close() won't mangle errno...
|
||||||
|
NM_RETURN_ERR("Failed to read KFMon's reply (%m)");
|
||||||
|
} else if (status == KFMON_IPC_LIST_PARSE_FAILURE) {
|
||||||
|
NM_RETURN_ERR("Failed to parse the list of watches (no separator found)");
|
||||||
|
} else if (status == KFMON_IPC_ERR_INVALID_ID) {
|
||||||
|
NM_RETURN_ERR("Requested to start an invalid watch index");
|
||||||
|
} else if (status == KFMON_IPC_ERR_INVALID_NAME) {
|
||||||
|
NM_RETURN_ERR("Requested to trigger an invalid watch filename (this is the image filename with, not the config) (maybe you forget the extension?)");
|
||||||
|
} else if (status == KFMON_IPC_WARN_ALREADY_RUNNING) {
|
||||||
|
NM_RETURN_ERR("Requested watch is already running");
|
||||||
|
} else if (status == KFMON_IPC_WARN_SPAWN_BLOCKED) {
|
||||||
|
NM_RETURN_ERR("A spawn blocker is currently running");
|
||||||
|
} else if (status == KFMON_IPC_WARN_SPAWN_INHIBITED) {
|
||||||
|
NM_RETURN_ERR("Spawns are currently inhibited");
|
||||||
|
} else if (status == KFMON_IPC_ERR_REALLY_MALFORMED_CMD) {
|
||||||
|
NM_RETURN_ERR("KFMon couldn't parse our command");
|
||||||
|
} else if (status == KFMON_IPC_ERR_MALFORMED_CMD) {
|
||||||
|
NM_RETURN_ERR("Bad command syntax");
|
||||||
|
} else if (status == KFMON_IPC_ERR_INVALID_CMD) {
|
||||||
|
NM_RETURN_ERR("Command wasn't recognized by KFMon");
|
||||||
|
} else if (status == KFMON_IPC_UNKNOWN_REPLY) {
|
||||||
|
NM_RETURN_ERR("We couldn't make sense of KFMon's reply");
|
||||||
|
} else {
|
||||||
|
// Should never happen
|
||||||
|
NM_RETURN_ERR("Something went wrong");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NM_RETURN_OK(nm_action_result_silent());
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef NM_ERR_RET
|
||||||
|
}
|
||||||
79
src/kfmon.h
Normal file
79
src/kfmon.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef NM_KFMON_H
|
||||||
|
#define NM_KFMON_H
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "action.h"
|
||||||
|
|
||||||
|
// Path to KFMon's IPC Unix socket
|
||||||
|
#define KFMON_IPC_SOCKET "/tmp/kfmon-ipc.ctl"
|
||||||
|
|
||||||
|
// Flags for the failure bingo bitmask
|
||||||
|
#define KFMON_IPC_ETIMEDOUT (1 << 1)
|
||||||
|
#define KFMON_IPC_EPIPE (1 << 2)
|
||||||
|
#define KFMON_IPC_ENODATA (1 << 3)
|
||||||
|
// syscall failures
|
||||||
|
#define KFMON_IPC_READ_FAILURE (1 << 4)
|
||||||
|
#define KFMON_IPC_SEND_FAILURE (1 << 5)
|
||||||
|
#define KFMON_IPC_SOCKET_FAILURE (1 << 6)
|
||||||
|
#define KFMON_IPC_CONNECT_FAILURE (1 << 7)
|
||||||
|
#define KFMON_IPC_POLL_FAILURE (1 << 8)
|
||||||
|
#define KFMON_IPC_CALLOC_FAILURE (1 << 9)
|
||||||
|
#define KFMON_IPC_REPLY_READ_FAILURE (1 << 10)
|
||||||
|
#define KFMON_IPC_LIST_PARSE_FAILURE (1 << 11)
|
||||||
|
// Those match the actual string sent over the wire
|
||||||
|
#define KFMON_IPC_ERR_INVALID_ID (1 << 12)
|
||||||
|
#define KFMON_IPC_ERR_INVALID_NAME (1 << 13)
|
||||||
|
#define KFMON_IPC_WARN_ALREADY_RUNNING (1 << 14)
|
||||||
|
#define KFMON_IPC_WARN_SPAWN_BLOCKED (1 << 15)
|
||||||
|
#define KFMON_IPC_WARN_SPAWN_INHIBITED (1 << 16)
|
||||||
|
#define KFMON_IPC_ERR_REALLY_MALFORMED_CMD (1 << 17)
|
||||||
|
#define KFMON_IPC_ERR_MALFORMED_CMD (1 << 18)
|
||||||
|
#define KFMON_IPC_ERR_INVALID_CMD (1 << 19)
|
||||||
|
// Not an error ;p
|
||||||
|
//#define KFMON_IPC_OK
|
||||||
|
#define KFMON_IPC_UNKNOWN_REPLY (1 << 20)
|
||||||
|
// Not an error either, needs we have more to read...
|
||||||
|
#define KFMON_IPC_EAGAIN (1 << 21)
|
||||||
|
|
||||||
|
// A single watch item
|
||||||
|
typedef struct {
|
||||||
|
uint8_t idx;
|
||||||
|
char *filename;
|
||||||
|
char *label;
|
||||||
|
} kfmon_watch_t;
|
||||||
|
|
||||||
|
// A node in a linked list of watches
|
||||||
|
typedef struct kfmon_watch_node {
|
||||||
|
kfmon_watch_t watch;
|
||||||
|
struct kfmon_watch_node* next;
|
||||||
|
} kfmon_watch_node_t;
|
||||||
|
|
||||||
|
// A control structure to keep track of a list of watches
|
||||||
|
typedef struct {
|
||||||
|
size_t count;
|
||||||
|
kfmon_watch_node_t* head;
|
||||||
|
kfmon_watch_node_t* tail;
|
||||||
|
} kfmon_watch_list_t;
|
||||||
|
|
||||||
|
// Used as the reply handler in our polling loops.
|
||||||
|
// Second argument is an opaque pointer used for storage in a linked list
|
||||||
|
// (e.g., a pointer to a kfmon_watch_list_t, or NULL if no storage is needed).
|
||||||
|
typedef int (*ipc_handler_t)(int, void *);
|
||||||
|
|
||||||
|
// Given one of the error codes listed above, return properly from an action. Success is silent.
|
||||||
|
nm_action_result_t* nm_kfmon_return_handler(int error, char **err_out);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// PoC list test action
|
||||||
|
int nm_kfmon_list_request(const char *restrict foo);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
222
src/kfmon_helpers.h
Normal file
222
src/kfmon_helpers.h
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/* $OpenBSD: atomicio.c,v 1.30 2019/01/24 02:42:23 dtucker Exp $ */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2006 Damien Miller. All rights reserved.
|
||||||
|
* Copyright (c) 2005 Anil Madhavapeddy. All rights reserved.
|
||||||
|
* Copyright (c) 1995,1999 Theo de Raadt. All rights reserved.
|
||||||
|
* All rights reserved.
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NOTE: Originally imported from https://github.com/openssh/openssh-portable/blob/master/atomicio.c
|
||||||
|
// Rejigged for my own use, with inspiration from git's own read/write wrappers,
|
||||||
|
// as well as gnulib's and busybox's
|
||||||
|
// c.f., https://github.com/git/git/blob/master/wrapper.c
|
||||||
|
// https://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/safe-read.c
|
||||||
|
// https://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/full-write.c
|
||||||
|
// https://git.busybox.net/busybox/tree/libbb/read.c
|
||||||
|
|
||||||
|
#ifndef NM_KFMON_HELPERS_H
|
||||||
|
#define NM_KFMON_HELPERS_H
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// Clamp IO chunks to the smallest of 8 MiB and SSIZE_MAX,
|
||||||
|
// to deal with various implementation quirks on really old Linux,
|
||||||
|
// macOS, or AIX/IRIX.
|
||||||
|
// c.f., git, gnulib & busybox for similar stories.
|
||||||
|
// Since we ourselves are 32 bit Linux-bound, 8 MiB suits us just fine.
|
||||||
|
#define MAX_IO_BUFSIZ (8 * 1024 * 1024)
|
||||||
|
#if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_BUFSIZ)
|
||||||
|
# undef MAX_IO_BUFSIZ
|
||||||
|
# define MAX_IO_BUFSIZ SSIZE_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// read() with retries on recoverable errors (via polling on EAGAIN).
|
||||||
|
// Not guaranteed to return len bytes, even on success (like read() itself).
|
||||||
|
// Always returns read()'s return value as-is.
|
||||||
|
static ssize_t xread(int fd, void* buf, size_t len) {
|
||||||
|
// Save a trip to EINVAL if len is large enough to make read() fail.
|
||||||
|
if (len > MAX_IO_BUFSIZ) {
|
||||||
|
len = MAX_IO_BUFSIZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ssize_t nr = read(fd, buf, len);
|
||||||
|
if (nr < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
|
||||||
|
poll(&pfd, 1, -1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write() with retries on recoverable errors (via polling on EAGAIN).
|
||||||
|
// Not guaranteed to write len bytes, even on success (like write() itself).
|
||||||
|
// Always returns write()'s return value as-is.
|
||||||
|
static __attribute__((unused)) ssize_t xwrite(int fd, const void* buf, size_t len) {
|
||||||
|
// Save a trip to EINVAL if len is large enough to make write() fail.
|
||||||
|
if (len > MAX_IO_BUFSIZ) {
|
||||||
|
len = MAX_IO_BUFSIZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ssize_t nw = write(fd, buf, len);
|
||||||
|
if (nw < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLOUT;
|
||||||
|
|
||||||
|
poll(&pfd, 1, -1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on OpenSSH's atomicio6, except we keep the return value/data type of the original call.
|
||||||
|
// Ensure all of data on socket comes through.
|
||||||
|
static __attribute__((unused)) ssize_t read_in_full(int fd, void* buf, size_t len) {
|
||||||
|
// Save a trip to EINVAL if len is large enough to make read() fail.
|
||||||
|
if (len > MAX_IO_BUFSIZ) {
|
||||||
|
len = MAX_IO_BUFSIZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* s = buf;
|
||||||
|
size_t pos = 0U;
|
||||||
|
while (len > pos) {
|
||||||
|
ssize_t nr = read(fd, s + pos, len - pos);
|
||||||
|
switch (nr) {
|
||||||
|
case -1:
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
|
||||||
|
poll(&pfd, 1, -1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 0:
|
||||||
|
// i.e., EoF/EoT
|
||||||
|
errno = EPIPE;
|
||||||
|
return (ssize_t) pos;
|
||||||
|
default:
|
||||||
|
pos += (size_t) nr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ssize_t) pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) ssize_t write_in_full(int fd, const void* buf, size_t len) {
|
||||||
|
// Save a trip to EINVAL if len is large enough to make write() fail.
|
||||||
|
if (len > MAX_IO_BUFSIZ) {
|
||||||
|
len = MAX_IO_BUFSIZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* s = buf;
|
||||||
|
size_t pos = 0U;
|
||||||
|
while (len > pos) {
|
||||||
|
ssize_t nw = write(fd, s + pos, len - pos);
|
||||||
|
switch (nw) {
|
||||||
|
case -1:
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLOUT;
|
||||||
|
|
||||||
|
poll(&pfd, 1, -1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
case 0:
|
||||||
|
// That only makes sense for regular files.
|
||||||
|
// On the other hand, write() returning 0 on !regular files is UB.
|
||||||
|
errno = ENOSPC;
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
pos += (size_t) nw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ssize_t) pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactly like write_in_full, but using send w/ flags set to MSG_NOSIGNAL,
|
||||||
|
// so we can handle EPIPE without having to deal with signals.
|
||||||
|
static ssize_t send_in_full(int sockfd, const void* buf, size_t len) {
|
||||||
|
// Save a trip to EINVAL if len is large enough to make send() fail.
|
||||||
|
if (len > MAX_IO_BUFSIZ) {
|
||||||
|
len = MAX_IO_BUFSIZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* s = buf;
|
||||||
|
size_t pos = 0U;
|
||||||
|
while (len > pos) {
|
||||||
|
ssize_t nw = send(sockfd, s + pos, len - pos, MSG_NOSIGNAL);
|
||||||
|
switch (nw) {
|
||||||
|
case -1:
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EAGAIN) {
|
||||||
|
struct pollfd pfd = { 0 };
|
||||||
|
pfd.fd = sockfd;
|
||||||
|
pfd.events = POLLOUT;
|
||||||
|
|
||||||
|
poll(&pfd, 1, -1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
pos += (size_t) nw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ssize_t) pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
12
src/util.h
12
src/util.h
@@ -19,22 +19,22 @@ extern "C" {
|
|||||||
// NM_RETURN returns ret, and if ret is NM_ERR_RET and err_out is not NULL, it
|
// 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
|
// writes the formatted error message to *err_out as a malloc'd string. The
|
||||||
// arguments may or may not be evaluated more than once.
|
// arguments may or may not be evaluated more than once.
|
||||||
#define NM_RETURN(ret, fmt, ...) do { \
|
#define NM_RETURN(ret, fmt, ...) ({ \
|
||||||
typeof(ret) _ret = (ret); \
|
__typeof__(ret) _ret = (ret); \
|
||||||
if (err_out) { \
|
if (err_out) { \
|
||||||
if (_ret == NM_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
|
if (_ret == NM_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
|
||||||
else *err_out = NULL; \
|
else *err_out = NULL; \
|
||||||
} \
|
} \
|
||||||
return _ret; \
|
return _ret; \
|
||||||
} while (0)
|
})
|
||||||
|
|
||||||
// NM_ASSERT is like assert, but it writes the formatted error message to
|
// 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
|
// 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
|
// evaluated exactly once. The other arguments may or may not be evaluated one
|
||||||
// or more times.
|
// or more times.
|
||||||
#define NM_ASSERT(cond, fmt, ...) do { \
|
#define NM_ASSERT(cond, fmt, ...) ({ \
|
||||||
if (!(cond)) NM_RETURN(NM_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
|
if (!(cond)) NM_RETURN(NM_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
|
||||||
} while (0)
|
})
|
||||||
|
|
||||||
// NM_RETURN_ERR is the same as NM_RETURN(NM_ERR_RET, fmt, ...).
|
// NM_RETURN_ERR is the same as NM_RETURN(NM_ERR_RET, fmt, ...).
|
||||||
#define NM_RETURN_ERR(fmt, ...) NM_RETURN(NM_ERR_RET, fmt, ##__VA_ARGS__)
|
#define NM_RETURN_ERR(fmt, ...) NM_RETURN(NM_ERR_RET, fmt, ##__VA_ARGS__)
|
||||||
|
|||||||
Reference in New Issue
Block a user