11

Trying to monitor 3 different folders and run a script when one changes. The problem is the script needs to know which folder changed, and I can't seem to find a way to pass that.

[Path]
PathChanged=/x/y/z
PathChanged=/a/b/c
PathChanged=/foo/bar
Unit=123.service

[Install]
WantedBy=multi-user.target

I'm assuming there just isn't a way to do it. And that I need to either have 3 separate .path files(gross), or have the script just iterate through all 3 folders every time one of them changes(inefficient and also gross).

But I figured I'd ask here. Perhaps there's a systemd variable I'm missing, or a more efficient way to do it without systemd. So is there?

Thanks.

3
  • «way without systemd» : you want inotify?? Commented Jul 28, 2020 at 1:15
  • Yeah, looks like I do. Found this similar question: link and looks like they gave up on it. Such a weird omission. Commented Jul 28, 2020 at 1:38
  • 1
    systemd.path(5) says: "Internally, path units use the inotify(7) API to monitor file systems." Commented Jul 28, 2020 at 8:15

2 Answers 2

23

After some playing I found the easiest way was to use one *.path file per path and template each path into a single *@.service file. Here's something using your example:

$ systemctl --user cat 123* *.path
# /home/stew/.config/systemd/user/[email protected]
[Service]
Type=oneshot
ExecStart=/bin/echo %I

# /home/stew/.config/systemd/user/abc.path
[Path]
PathChanged=/a/b/c
[email protected]

# /home/stew/.config/systemd/user/foobar.path
[Path]
PathChanged=/foo/bar
[email protected]

# /home/stew/.config/systemd/user/xyz.path
[Path]
PathChanged=/x/y/z
[email protected]

The *.service can access the path through the %I specifier

To get theUnit= names, I used systemd-escape:

$ systemd-escape [email protected] \
      '/x/y/z' \
      '/a/b/c' \
      '/foo/bar'
[email protected] [email protected] [email protected]

Relevant man pages:

In case you're wondering whether there is an easier solution, here are the things I tried:


Experiment 1

Hypothesis: It's in an environment variable.

systemd.exec(5) gives a list of environment variables. It's possible that something like $RUNTIME_DIRECTORY or $LISTEN_FDS is set.

Experiment Setup:

$ mkdir /home/stew/systemdpath
$ systemctl --user cat simplepath.*
# /home/stew/.config/systemd/user/simplepath.path
[Unit]
Description=Path testing

[Path]
DirectoryNotEmpty=/home/stew/systemdpath

# /home/stew/.config/systemd/user/simplepath.service
[Unit]
Description=Path testing unit

[Service]
Type=oneshot
ExecStart=/usr/bin/env
$ systemctl --user start simplepath.path

Experiment Results:

$ touch ~/systemdpath/file
$ journalctl --user simplepath.service
Jul 28 08:26:16 stewbian systemd[31634]: Starting Path testing unit...
Jul 28 08:26:16 stewbian env[334512]: HOME=/home/stew
Jul 28 08:26:16 stewbian env[334512]: LANG=en_GB.UTF-8
Jul 28 08:26:16 stewbian env[334512]: LANGUAGE=en_GB:en
Jul 28 08:26:16 stewbian env[334512]: LOGNAME=stew
Jul 28 08:26:16 stewbian env[334512]: PATH=/home/stew/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Jul 28 08:26:16 stewbian env[334512]: SHELL=/bin/bash
Jul 28 08:26:16 stewbian env[334512]: USER=stew
Jul 28 08:26:16 stewbian env[334512]: XDG_RUNTIME_DIR=/run/user/1000
Jul 28 08:26:16 stewbian env[334512]: GTK_MODULES=gail:atk-bridge
Jul 28 08:26:16 stewbian env[334512]: QT_ACCESSIBILITY=1
Jul 28 08:26:16 stewbian env[334512]: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
Jul 28 08:26:16 stewbian env[334512]: DESKTOP_SESSION=/usr/share/xsessions/i3
Jul 28 08:26:16 stewbian env[334512]: DISPLAY=:0
Jul 28 08:26:16 stewbian env[334512]: GPG_AGENT_INFO=/run/user/1000/gnupg/S.gpg-agent:0:1
Jul 28 08:26:16 stewbian env[334512]: PAM_KWALLET5_LOGIN=/run/user/1000/kwallet5.socket
Jul 28 08:26:16 stewbian env[334512]: PWD=/home/stew
Jul 28 08:26:16 stewbian env[334512]: SHLVL=1
Jul 28 08:26:16 stewbian env[334512]: XAUTHORITY=/home/stew/.Xauthority
Jul 28 08:26:16 stewbian env[334512]: XDG_CURRENT_DESKTOP=i3
Jul 28 08:26:16 stewbian env[334512]: XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
Jul 28 08:26:16 stewbian env[334512]: XDG_SESSION_CLASS=user
Jul 28 08:26:16 stewbian env[334512]: XDG_SESSION_DESKTOP=i3
Jul 28 08:26:16 stewbian env[334512]: XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session5
Jul 28 08:26:16 stewbian env[334512]: XDG_SESSION_TYPE=x11
Jul 28 08:26:16 stewbian env[334512]: _=/usr/bin/dbus-update-activation-environment
Jul 28 08:26:16 stewbian env[334512]: MANAGERPID=31634
Jul 28 08:26:16 stewbian env[334512]: INVOCATION_ID=837f6b2e56b543c9b51cda4ee8952fa8
Jul 28 08:26:16 stewbian env[334512]: JOURNAL_STREAM=8:21581980
Jul 28 08:26:16 stewbian systemd[31634]: simplepath.service: Succeeded.
Jul 28 08:26:16 stewbian systemd[31634]: Finished Path testing unit

Conclusion:

Systemd does not put the path into an environment variable.


Experiment 2

Hypothesis: It can be templated

Thinking of $LISTEN_FDS there are some parallels between socket and paths. Sockets are templated when Accept=yes is set, so what if we try that with paths?

Initial setup:

$ systemctl --user cat simplepath*
# /home/stew/.config/systemd/user/simplepath.path
[Unit]
Description=Path testing

[Path]
DirectoryNotEmpty=/home/stew/systemdpath
[email protected]

# /home/stew/.config/systemd/user/[email protected]
[Unit]
Description=Path testing unit

[Service]
Type=oneshot
ExecStart=/bin/echo %i

Experiment Results:

$ systemctl --user start simplepath.path
$ touch ~/systemdpath/file
$ journalctl --user --since "5 minutes ago"
Jul 28 09:14:25 stewbian systemd[31634]: Starting Path testing unit...
Jul 28 09:14:25 stewbian echo[336171]: simplepath
Jul 28 09:14:25 stewbian systemd[31634]: [email protected]: Succeeded.

Conclusion

The instance echo'd in the service was the service name itself. That doesn't help.


Experiment 3

Hypothesis: Each path can have its own file and template the service

Experiment Setup:

$ mkdir ~/path1
$ mkdir ~/path2
$ systemctl --user cat path*
# /home/stew/.config/systemd/user/path1.path
[Unit]
Description=Path1 testing

[Path]
DirectoryNotEmpty=%h/path1
[email protected]

# /home/stew/.config/systemd/user/path2.path
[Unit]
Description=Path2 testing

[Path]
DirectoryNotEmpty=%h/path2
[email protected]

# /home/stew/.config/systemd/user/[email protected]
[Unit]
Description=Path testing unit

[Service]
Type=oneshot
ExecStart=/bin/echo %h/%i
$ systemctl --user start path1.path path2.path

Experiment:

$ touch ~/path1
$ touch ~/path2
$ journalctl --user --since "5 minutes ago"
Jul 28 09:43:45 stewbian systemd[31634]: Starting Path testing unit...
Jul 28 09:43:45 stewbian echo[336517]: /home/stew/path1
Jul 28 09:43:45 stewbian systemd[31634]: [email protected]: Succeeded.
Jul 28 09:43:45 stewbian systemd[31634]: Finished Path testing unit.
Jul 28 09:43:50 stewbian systemd[31634]: Starting Path testing unit...
Jul 28 09:43:50 stewbian echo[336519]: /home/stew/path2
Jul 28 09:43:50 stewbian systemd[31634]: [email protected]: Succeeded.
Jul 28 09:43:50 stewbian systemd[31634]: Finished Path testing unit.

Conclusion

You can have a single templated service unit working for several path units. This seems to be the simplest approach.

The problem I ran into here is that the service uses %h which is the home directory. I had problems when I included the / character in the template names. systemd-escape(1) seems to help out with this.

4
  • 2
    Thank you very much for the extremely well detailed answer! Commented Jul 29, 2020 at 20:57
  • Maybe the version of systemd used by @Stewart didn't provide TRIGGER_UNIT and TRIGGER_PATH environment variables for .path services in the past, but the version I just used (252.12) includes them. I tested this using rootless docker, not sure if it makes a difference. Commented Aug 23, 2023 at 13:03
  • Upon further investigation TRIGGER_PATH always shows the first PathChanged= path in the list even if I touch one of the other directories. This seems like a bug! Commented Aug 23, 2023 at 13:18
  • It is a non-bug: github.com/systemd/systemd/issues/28939 Commented Jan 30 at 13:49
0

On some Linux before the introduction of systemd (like RedHat 6.x) we could install and use incrontab command which is much simpler and complete than systemd.path solution. Incrontab and incrond provide a powerful interface to inotify api but unfortunatly it was replaced by systemd.path which is incomplete compared with incrontab. I guess incrontab can be still installed on current releases that use systemd, but it's by far unmaintained, so, it isn't elegible for a production and or critical system. I hope this to be fixed in future releases.

1
  • 2
    This would be a better answer if you described how to use incrontab to solve the OP’s problem. Commented Oct 9, 2020 at 23:50

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.