1
0

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:
NiLuJe
2020-05-01 15:05:36 -04:00
parent dc698bfc64
commit 3639fc487c
9 changed files with 844 additions and 16 deletions

8
.gitignore vendored
View File

@@ -1,4 +1,11 @@
# make gitignore
# KDevelop
.kdev4/
NickelMenu.kdev4
.kateconfig
# Build artefacts
/KoboRoot.tgz
/src/libnm.so
@@ -9,6 +16,7 @@
/src/failsafe.o
/src/init.o
/src/config.o
/src/kfmon.o
/src/action.o
/src/dlhook.o

View File

@@ -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
endif
define GITIGNORE_HEAD
# make gitignore
# KDevelop
.kdev4/
NickelMenu.kdev4
.kateconfig
# Build artefacts
endef
export GITIGNORE_HEAD
all: src/libnm.so
clean:
rm -f $(GENERATED)
gitignore:
echo '# make gitignore' > .gitignore
echo "$${GITIGNORE_HEAD}" > .gitignore
echo '$(GENERATED)' | \
sed 's/ /\n/g' | \
sed 's/^./\/&/' >> .gitignore
@@ -65,7 +77,7 @@ override GENERATED += KoboRoot.tgz
src/libnm.so: override CFLAGS += $(PTHREAD_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: 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 MOCS += src/qtplugin.moc

View File

@@ -23,7 +23,7 @@
# dbg_error - always returns an error (for testing)
# dbg_msg - shows a message (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_extras - opens one of the beta features
# nickel_misc - other stuff which isn't significant enough for its own category
@@ -34,7 +34,9 @@
# dbg_error - the error message
# dbg_msg - 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:
# invert - toggles FeatureSettings.InvertScreen (all versions)
# lockscreen - toggles PowerSettings.UnlockEnabled (4.12.12111+)
@@ -66,6 +68,7 @@
# menu_item :main :Do Nothing :cmd_spawn :sleep 60
# menu_item :main :Kernel Version :cmd_output :500:uname -a
# menu_item :main :Sketch Pad :nickel_extras :Sketch Pad
# menu_item :main :Plato :kfmon :plato.png
# menu_item :reader :Invert Screen :nickel_setting :invert
#
# You will need to reboot to see any changes.

View File

@@ -36,6 +36,8 @@ void nm_action_result_free(nm_action_result_t *res);
X(dbg_msg) \
X(dbg_toast) \
X(kfmon) \
X(kfmon_id) \
X(kfmon_list) \
X(nickel_setting) \
X(nickel_extras) \
X(nickel_misc) \

View File

@@ -1,6 +1,7 @@
#define _GNU_SOURCE // asprintf
#include "action.h"
#include "kfmon.h"
#include "util.h"
NM_ACTION_(dbg_syslog) {
@@ -28,8 +29,27 @@ NM_ACTION_(dbg_toast) {
#undef NM_ERR_RET
}
NM_ACTION_(kfmon) {
#define NM_ERR_RET NULL
NM_RETURN_ERR("not implemented yet (arg=%s)", arg); // TODO
#undef NM_ERR_RET
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);
}
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
View 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
View 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
View 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

View File

@@ -19,22 +19,22 @@ extern "C" {
// 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, ...) do { \
typeof(ret) _ret = (ret); \
#define NM_RETURN(ret, fmt, ...) ({ \
__typeof__(ret) _ret = (ret); \
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; \
} \
return _ret; \
} while (0)
})
// 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, ...) do { \
#define NM_ASSERT(cond, fmt, ...) ({ \
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, ...).
#define NM_RETURN_ERR(fmt, ...) NM_RETURN(NM_ERR_RET, fmt, ##__VA_ARGS__)