-
-
Notifications
You must be signed in to change notification settings - Fork 348
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2592 from jimklimov/issue-2591
Introduce a simple PoC for NUT readings seen in FUSE file system
- Loading branch information
Showing
7 changed files
with
261 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
NUT access via FUSE | ||
=================== | ||
|
||
After a discussion with friends about the | ||
link:https://en.wikipedia.org/wiki/Filesystem_in_Userspace[FUSE] technology | ||
an idea resurfaced, to have NUT readings available as filesystem objects. | ||
|
||
Previously this was vaguely envisioned as a sort of `/dev/shm` on Linux or | ||
equivalent on other OSes (where SHM is available with a filesystem API). | ||
But FUSE being more portable, it is actually a better bet for this approach. | ||
|
||
* https://github.com/networkupstools/nut/issues/2591 | ||
NUT access via execfuse | ||
~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
For a proof-of-concept, the link:https://github.com/vi/execfuse[execfuse] | ||
seemed appropriate. It is a daemon which knows to call user-provided programs | ||
to handle certain filesystem API requests. Essentially, shell scripts exposed | ||
as a file system! | ||
|
||
Sure, it is not very efficient or perhaps even secure, but for quick and dirty | ||
experiments it proved to be great! Relatively easy to debug the new code by | ||
saving log files, and quick to iterate by editing the shell scripts (no need | ||
to rebuild and restart a daemon). | ||
|
||
Installation: | ||
|
||
---- | ||
:; git clone https://github.com/vi/execfuse | ||
:; cd execfuse | ||
:; make | ||
---- | ||
|
||
That's about it, an `execfuse` binary should appear. | ||
|
||
To run with the PoC scripts here, assuming a built NUT in `~/nut` location: | ||
|
||
---- | ||
:; mkdir /tmp/nut-fuse | ||
:; PATH="$HOME/nut/clients:$PATH" \ | ||
./execfuse "$HOME/nut/scripts/fuse/execfuse-nut" /tmp/nut-fuse | ||
---- | ||
|
||
Then as you walk the `/tmp/nut-fuse` location, you would see a sub-directory | ||
called `by-server` under which a `localhost:3493` is exposed and any token | ||
(even if not visible) can be accessed with the naive assumption that it is | ||
a hostname/IP address and an optional port. | ||
|
||
Either way, this token is also treated as a directory, assuming it is a | ||
device name known to the data server `upsd` on that host. In fact, this | ||
directory would be populated by `upsc -l` of that host(:port) automatically. | ||
|
||
Each device represented this way is also a "directory", with "files" in it | ||
populated by `upsc` queries to list the currently known variables, and file | ||
contents populated by `upsc` queries for that variable name at that device | ||
on that server. | ||
|
||
Example data walk: | ||
|
||
---- | ||
:; grep -rH /tmp/nut-fuse/localhost/dummy | ||
/tmp/nut-fuse/localhost/dummy/battery.charge:100 | ||
/tmp/nut-fuse/localhost/dummy/battery.charge.low:20 | ||
/tmp/nut-fuse/localhost/dummy/battery.runtime:1456 | ||
/tmp/nut-fuse/localhost/dummy/battery.type:PbAc | ||
... | ||
---- | ||
|
||
As another experiment, a root-directory pseudo-file was added to reveal | ||
the `upsc` client location and version used in the queries, just for kicks: | ||
|
||
---- | ||
:; ls -la /tmp/nut-fuse | ||
total 23 | ||
drwxr-xr-x 16 root root 4096 Jan 1 1970 . | ||
drwxrwxrwt 154 root root 20480 Aug 13 23:55 .. | ||
drwxr-xr-x 16 root root 4096 Jan 1 1970 by-server | ||
-r--r--r-- 1 root root 0 Jan 1 1970 client-version | ||
:; cat /tmp/nut-fuse/client-version | ||
/home/abuild/nut/clients/upsc | ||
Network UPS Tools upsc 2.8.2.901.2-903-g533aa7eaf (development iteration after 2.8.2) | ||
---- | ||
|
||
Eventually, to stop this experiment: | ||
|
||
---- | ||
:; fusermount -u /tmp/nut-fuse | ||
---- | ||
|
||
As far as a PoC goes, this is already a fun and quickly achieved result, | ||
although for production use something like a FUSE-capable `upsmon`-like | ||
client would be needed both for efficiency (to avoid dozens of process | ||
forks and networked queries to get each reading), and to deal with security | ||
somehow (for a shot at `upsrw` and `upscmd` equivalents). | ||
|
||
Hope this helps get the real development going, | ||
Jim Klimov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#!/bin/bash | ||
|
||
# Simple PoC of NUT client as a FUSE mountable filesystem | ||
# Requires https://github.com/vi/execfuse | ||
# Copyright (C) 2024 by Jim Klimov <[email protected]> | ||
# Licensed GPLv2+ as NUT codebase | ||
|
||
if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then | ||
exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" | ||
echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 | ||
fi | ||
|
||
D="" | ||
F="" | ||
case "$1" in | ||
/|/by-server) | ||
D="$1" | ||
;; | ||
/by-server/*/*/*/*) # Don't want subdirs here | ||
;; | ||
/by-server/*/*/*) | ||
# VARNAME | ||
F="`basename "$1"`" | ||
# UPSSRV/UPSNAME | ||
D="`echo "$1" | sed 's,^/by-server/\(.*\)/'"$F"'$,\1,'`" | ||
;; | ||
/by-server/*/*) | ||
# UPSSRV/UPSNAME | ||
D="`echo "$1" | sed 's,^/by-server/\(.*\)$,\1,'`" | ||
;; | ||
/by-server/*) # No further slash | ||
# UPSSRV | ||
D="`basename "$1"`" | ||
;; | ||
/client-version) | ||
F="$1" | ||
;; | ||
*) | ||
;; | ||
esac | ||
|
||
if [ -n "$F" ] ; then | ||
printf 'ino=1 mode=-r--r--r-- nlink=1 uid=0 gid=0 rdev=0 size=0 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 %s\0' "$1" | ||
else | ||
if [ -n "$D" ] ; then | ||
printf 'ino=1 mode=drwxr-xr-x nlink=16 uid=0 gid=0 rdev=0 size=4096 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 %s\0' "$1" | ||
else | ||
exit 1 | ||
fi | ||
fi | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
#!/bin/sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#!/bin/bash | ||
|
||
# Simple PoC of NUT client as a FUSE mountable filesystem | ||
# Requires https://github.com/vi/execfuse | ||
# Copyright (C) 2024 by Jim Klimov <[email protected]> | ||
# Licensed GPLv2+ as NUT codebase | ||
|
||
if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then | ||
exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" | ||
echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 | ||
fi | ||
|
||
#PATH="~/nut/clients:$PATH" | ||
#export PATH | ||
|
||
case "$1" in | ||
/by-server/*/*/*/*) # Don't want subdirs here | ||
;; | ||
/by-server/*/*/*) | ||
VARNAME="`basename "$1"`" | ||
UPS="`echo "$1" | sed 's,^/by-server/\([^/]*\)/\([^/]*\)/'"$VARNAME"'$,\2@\1,'`" | ||
if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then | ||
#echo "+upsc '$UPS' '$VARNAME'" >&2 | ||
set -x | ||
fi | ||
upsc "$UPS" "$VARNAME" | ||
exit $? | ||
;; | ||
/client-version) | ||
if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then | ||
#echo "+upsc '$UPS' '$VARNAME'" >&2 | ||
set -x | ||
fi | ||
command -v upsc | ||
NUT_DEBUG_LEVEL=6 upsc -V | ||
exit $? | ||
;; | ||
esac | ||
|
||
exit 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#!/bin/bash | ||
|
||
# Simple PoC of NUT client as a FUSE mountable filesystem | ||
# Requires https://github.com/vi/execfuse | ||
# Copyright (C) 2024 by Jim Klimov <[email protected]> | ||
# Licensed GPLv2+ as NUT codebase | ||
|
||
if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then | ||
exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" | ||
echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 | ||
#echo "ARGS: $#: $@" >&2 | ||
#set | grep -E '^[^ =]*=' >&2 | ||
fi | ||
|
||
#PATH="~/nut/clients:$PATH" | ||
#export PATH | ||
|
||
#exec find "$2$1" -mindepth 1 -maxdepth 1 -printf 'ino=%i mode=%M nlink=%n uid=%U gid=%G rdev=0 size=%s blksize=512 blocks=%b atime=%A@ mtime=%T@ ctime=%C@ %f\0' | ||
|
||
# $1 = (parent?) dirname | ||
# $2 = dir basename? | ||
|
||
print_dots() { | ||
printf 'ino=1 mode=drwxr-xr-x nlink=4 uid=0 gid=0 rdev=0 size=4096 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 .\0' | ||
printf 'ino=1 mode=drwxr-xr-x nlink=4 uid=0 gid=0 rdev=0 size=1111 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 ..\0' | ||
} | ||
|
||
case "$1" in | ||
/) | ||
print_dots | ||
for D in "by-server" ; do | ||
printf 'ino=1 mode=drwxr-xr-x nlink=4 uid=0 gid=0 rdev=0 size=4096 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 %s\0' "$D" | ||
done | ||
for F in "client-version" ; do | ||
printf 'ino=1 mode=-r--r--r-- nlink=1 uid=0 gid=0 rdev=0 size=4096 blksize=512 blocks=2 atime=0 mtime=0 ctime=0 %s\0' "$F" | ||
done | ||
;; | ||
/by-server) | ||
print_dots | ||
printf 'ino=1 mode=drwxr-xr-x nlink=16 uid=0 gid=0 rdev=0 size=16 blksize=512 blocks=1 atime=0 mtime=0 ctime=0 %s\0' "localhost:3493" | ||
;; | ||
/by-server/*/*/*) # Don't want subdirs here | ||
exit 2 # ENOENT | ||
;; | ||
/by-server/*/*) # list device variables | ||
UPSNAME="`basename "$1"`" | ||
UPSSRV="`echo "$1" | sed 's,^/by-server/\(.*\)/'"$UPSNAME"'$,\1,'`" | ||
print_dots | ||
for VARNAME in `upsc "$UPSNAME@$UPSSRV" | awk -F: '{print $1}'` ; do | ||
printf 'ino=1 mode=-r--r--r-- nlink=1 uid=0 gid=0 rdev=0 size=16 blksize=512 blocks=1 atime=0 mtime=0 ctime=0 %s\0' "$VARNAME" | ||
done | ||
;; | ||
/by-server/*) # devices on hostname | ||
UPSSRV="`basename "$1"`" | ||
print_dots | ||
for UPSNAME in `upsc -l "$UPSSRV"` ; do | ||
printf 'ino=1 mode=drwxr-xr-x nlink=16 uid=0 gid=0 rdev=0 size=16 blksize=512 blocks=1 atime=0 mtime=0 ctime=0 %s\0' "$UPSNAME" | ||
done | ||
;; | ||
*) | ||
exit 2 # ENOENT | ||
;; | ||
esac | ||
|
||
exit 0 |