troglobit / finit

Fast init for Linux. Cookies included
https://troglobit.com/projects/finit/
MIT License
632 stars 63 forks source link

Add condition for "device exists" e.g. <dev/ttyUSB1> for controlling tty/service/run/task #185

Closed hongkongkiwi closed 1 year ago

hongkongkiwi commented 3 years ago

I've got a service for gpsd, I want to ensure that /dev/ttyUSB1 exists as a condition before this service will run.

I notice that there is <net/<DEVICE>/exists> but no <dev/<DEVICE>/exists>. I am hoping to have a condition like <dev/ttyUSB1/exists> as a condition for gpsd. Is this possible?

As an aside, how about a generic file or directory exists condition, e.g. file//exists ? That could probably work for a device too.

troglobit commented 3 years ago

A generic file/foo/exists was tried a while ago, but scrapped due to the added complexity in Finit. There are usr conditions you can use instead, and possibly an mdev script to enable the service when ttyUSB1 is plugged in. That's what I'd recommend to use instead.

hongkongkiwi commented 3 years ago

I think a file/dir exists or file/dir modified would be really handy, but I totally understand why it would be scrapped. The same could be achieved in multiple ways.

I guess your talking about issue #135 for this.

When you clear an a condition that an app relies on, does Finit stop that app? Or does it require an additional initctl stop

troglobit commented 3 years ago

Loss of conditions stops services, yes.

hongkongkiwi commented 2 years ago

I am able to handle this with custom conditions and mdev, although I would love to have a <dev/something> in future.

troglobit commented 2 years ago

What we have, which I forgot to mention, is a tty.so plugin, which can start a getty on demand, e.g. when someone plugs in a usb-to-serial adapter. However, now (years ago but ok) that we've refactored tty's into a service variant, maybe your idea isn't so far-fetched after all ... reopening.

hongkongkiwi commented 2 years ago

I was thinking about this and maybe it could be turned into a more generic thing. I think this would be helpful.

For example, say you wanted dropbear to be started when a file is created, e.g. /run/dropbear so you could specify a condition: <file/run/dropbear/exist> and finit would hold the inotify for that path. This would extend into <dir/dev/ttyUSB2/exist> naturally.

As an extension to this, you could have this: <file/etc/mosquitto.conf/update> which would reload the service when that file is updated.

This would turn it into a very flexible wrapper of inotify.

I re-read above and realised that this used to be here but was scrapped, just musing again about the device functionality. I'm also using files in /run for some basic info (a sort of text db) so was thinking that would also be helpful.

troglobit commented 2 years ago

This would indeed be very powerful.

Problem is inotify in the kernel listens to directories, so there could potentially be lots of activity to discard for each such file monitor ... however, this could be handled by a helper daemon, which in turn could generate the conditions Finit reacts to. Finit could "simply" instruct it's helper daemon listen to a certain set of paths after it has read its .conf files and figured out all the current <file/foo/state> conditions.

Did you misspell above <dir/dev/ttyUSB2/exist> or am I missing a point here? Otherwise I'll guess /dev/ttyUSB2 is a file, and you meant <file/...>.

This is quite a lot of work though, and I have clients eagerly awaiting v4.3 right now, so even though it's intriguing it'll have to wait for the next release cycle.

hongkongkiwi commented 2 years ago

How do you think if we narrowed down this feature request to being able to set custom watches on paths in the config? For example in the config we could have:

watch_path=/dev/ttyUSB2
watch_path=/my/custom/filepath

We could even have this as a compile time option if it's easier? (although more flexible if it can be in the config).

This could be limited to specific paths and then we are only adding a few extra watches for finit to monitor.

Then the condition could be <notify/path/exist>.

This makes it super flexible and you could even monitor something like /proc/device-tree/.... and have it set a condition based on whats in the dtb file.

troglobit commented 2 years ago

There are lots of possibilities and I've spent a great deal of time contemplating all this. I created Finit's keventd for handling <sys/...> conditions, primarily for tracking ACPI power state changes. I'm thinking it could be extended to handle other types of kernel NETLINK_KOBJECT_UEVENTs as well, e.g. devices being added/removed.

For generic files another helper daemon could be added, perhaps an inotifyd. Maybe inotifywatch can be used for this, which would make this part an integration/distributor thing rather than having to be supported natively by Finit. Documenting the API (/run/finit/cond/*) for the condition subsystem is something I've been considering for a while, and since my former employer use that heavily for their own conditions, it's effectively frozen.

hongkongkiwi commented 2 years ago

That’s a good idea, can I add my own conditions as files in /run/finit/cond ? Right now I’m setting conditions during startup using initctl (such as in mdev scripts), and it may be what’s causing timeouts for me with finit sometimes.

Could I directly add files into the conditions path and finit will pick it up without a reload?

troglobit commented 2 years ago

Yes, but hang on. In https://github.com/troglobit/finit/issues/254#issuecomment-1113945861 I detailed which initctl commands are supported in runlevel S, that don't block or communicate with Finit. User conditions with initctl cond [set | clear] are among those supported, so if you're setting/clearing/getting conditions, that's not what causing your timeouts.

Now, conditions come in two types: static and dynamic. Static conditions are just a symlink to the reconf file, while dynamic conditions are managed by a plugin that's responsible for stepping the "configuration generation" (numeric value in reconf) and signaling Finit etc. A dynamic condition can be set, and cleared but also in flux. Flux is the state in between configuration changes (generations)

The user conditions set with initctl are static, essentially initctl cond [set | clear] are wrappers for ln and rm:

root@anarchy:~# initctl cond set foo
root@anarchy:~# ls -l /run/finit/cond/usr/
total 0
lrwxrwxrwx    1 root     root            26 May  8 12:02 foo -> /var/run/finit/cond/reconf

Note: the initctl cond [status | dump] do connect to Finit to decorate the output with owners/asserters/origin of conditions.

hongkongkiwi commented 2 years ago

So if I wanted to have my own script that is run by mdevd for example to handle create/teardown of a static condition, could I do this?

mkdir -p "/run/finit/dev/ttyUSB2"
echo "1" > "/run/finit/dev/ttyUSB2/exist"

and for off

mkdir -p "/run/finit/dev/ttyUSB2"
echo "0" > "/run/finit/dev/ttyUSB2/exist"

and for flux

mkdir -p "/run/finit/dev/ttyUSB2"
echo "2" > "/run/finit/dev/ttyUSB2/exist"

I am currently doing this in user conditions but it's getting a bit polluted, so I'd rather have a very clear definition of it.

troglobit commented 2 years ago

No, the 0, 1, 2 are configuration generations, not state numbers. Also I'm not sure you need the flux state, supporting that is quite a bit more involved and nothing I recommend trying to do outside of the plugin world -- and not before I've documented it fully (lot's of booby traps). Sorry for not being clear on this.

What you can do, however, is static conditions. First of all, they need to be in the cond subdirectory and be a symlink back to reconf, as I mentioned above.

root@zero:/run/finit/cond# mkdir dev
root@zero:/run/finit/cond# mkdir dev/ttyUSB1
root@zero:/run/finit/cond# ln -s /var/run/finit/cond/reconf dev/ttyUSB1/exist
root@zero:/run/finit/cond# initctl -p cond dump
PID  IDENT        STATUS  CONDITION
===================================================
1    init         on      <dev/ttyUSB1/exist>
...

Removing the symlink removes the condition.

hongkongkiwi commented 1 year ago

I was able to work around this by using mdevd scripts so it's less pressing, however I do think that adds extra complexity that might actually be a great match with finit as a core feature.

Systemd does this (obviously we don't want to replicate all the features of that beast), but many services do have a dependency on devices being available or not. And since finit runs the earliest and is keeping track of a few dev devices already (primarily networking).

Some usecases for example:

Many well behaved services will poll their device for availability and handle ones that become unavailable, but in some cases it might be better if they are not running at all.

These are all pretty valid use cases where you want a device to be up before finit starts the service.

troglobit commented 1 year ago

OK, I'll have a look at it. If we limit the scope to just <dev/foo> and <dev/bar/baz> we can copy the pidfile plugin and create a new class of conditions, but it would be based entirely on inotify and not the kernel plug events. Additionally, conditions are constrained to only work properly outside of runlevel S, 6, and 0, i.e. not guaranteed to work at bootstrap or shutdown. Would that be OK with you?

hongkongkiwi commented 1 year ago

Yup! I think that would work nicely.

troglobit commented 1 year ago

Great, thanks! I'll see what I can do.

troglobit commented 1 year ago

There, finally, a bare-bones <dev/foo> and <dev/bar/baz> monitor in place. No new configure flags, built-in and enabled by default. Documentation to be updated shortly.