スクリプトがUDEVトリガーとして呼び出されると/dev/ttyがなくなる

スクリプトがUDEVトリガーとして呼び出されると/dev/ttyがなくなる

私は標準の Raspbian がインストールされた Raspberry Pi を持っています。RetroPI もインストールしましたが、問題なく動作します。

私がやりたかったのは、ゲームパッドがBluetooth経由で接続されたときにエミュレーションステーション(retropiメイン実行スクリプト)を起動する小さな自動起動スクリプトを書くことです。私は簡単なudevルールを作成しました

pi@raspberrypi:~ $ cat /etc/udev/rules.d/99-zlocal.rules
SUBSYSTEM=="bluetooth",SUBSYSTEMS=="amba", ATTRS{id}=="00241011", ACTION=="add", RUN+="/usr/local/bin/emulator-controll.sh start"

期待どおりに動作し、ゲームパッドがペアリングされるとスクリプトが実行されます。

問題は、例えばSSH経由でスクリプトを実行すると、

sudo emulator-controll.sh start        #sudo is optional here, but I want the same conditions as in udev trigger context

予想通りのエミュレーションステーション。しかし、同じスクリプトを上記のudevルールのアクショントリガーとして使用すると失敗します。次のようなエラー

Wed Aug 21 00:02:33 CEST 2019
Invoking user: root
Invoked start command
Invoking emulation station start command
Done
'unknown': I need something more specific.
tput: unknown terminal "unknown"
Segmentation fault
/usr/bin/emulationstation: line 23: /dev/tty: No such device or address
tput: unknown terminal "unknown"

何らかの理由で (Linux 初心者ですが) tty が udev トリガー コンテキストからアクセスできないのだと思います。 何とか修正できますか?

PS. 起動時に起動するバックグラウンドデーモン(名前付きパイプを使用するため)を実行しようとしましたが、結果はまったく同じでした。スクリプト実行コンテキストに tty がないため、これはバックグラウンドタスクの一般的な動作であると思われます。

スクリプト自体は無関係だと私は思いますが、わかりやすくするためにこれも提供します。

#!/bin/bash
#/bin/date >> /tmp/udev.log
#env >> /tmp/udev.log
#echo "Connected" > /dev/pts/2

log=/var/log/emulator-controll.log

{
readarray -t PIDS <<< $(ps aux | grep [e]mulationstation | awk '{print $2}')
echo ">${PIDS[@]}<"
echo >> $log
/bin/date >> $log
echo "Invoking user: $(whoami)"
case "$1" in
        start )
                echo "Invoked start command" >> $log
                if [[ "${#PIDS[@]}" -ge 3 ]]; then
                        echo "Emulator is already running. No action is taken" >> $log
                else
                        echo "Invoking emulation station start command" >> $log
                        export DISPLAY=:0
                        sudo -u pi nohup emulationstation  &
                fi
                echo "Done" >> $log
                ;;
        stop )
                echo "Invoked stop command" >> $log
                if [[ -z "${PIDS[0]}" ]]; then
                        echo "Emulator is not running, no action is taken" >> $log
                else
                        echo "Killing processes with PIDs ${PIDS[@]}" >> $log
                        for pid in "${PIDS[@]}"; do
                                kill $pid
                        done
                        echo "Done" >> $log
                fi
                ;;
        *)
                echo "Usage: $0 {start|stop}"
esac

} >> $log 2>>$log

答え1

スクリプトを TTY にアタッチしたり、スクリプトに tty を割り当てたりする代わりに、次のようにしてコマンドを宛先 tty の入力バッファに直接挿入することで目的を達成できました。

sudo writevt /dev/tty1 "emulationstation $(echo -ne '\r')"

これはコマンドを入力し、メイン端末で「Enter」キーを押します。

PS. はRaspbian で利用可能 (少なくとも現時点では)writevtの一部です。console-tools

答え2

カーネルがアクションをトリガーし、udevルールの一部としてスクリプトを実行する場合、それらのスクリプトの環境は非常に制限されます。

1台のLinuxコンピュータに同時にログインできるユーザーは多数おり、各ユーザーは複数のttyを持っているため、もちろんカーネルから実行されるスクリプトには tty がありません。このスクリプトを開始したユーザーはいないので、たとえ tty を割り当てることができたとしても、使用中の多数の tty のうちどれを割り当てるべきでしょうか。これは X 接続についても同様です (これもよく尋ねられる質問です)。

他にも制限があります。以下をご覧くださいman 7 udev

走る

特定のデバイスで実行されるプログラムのリストにプログラムを追加します。これは、実行時間が短いタスクにのみ使用できます。イベント プロセスを長時間実行すると、このデバイスまたは依存デバイスの以降のすべてのイベントがブロックされる可能性があります。長時間実行されるタスクは、イベント プロセス自体からすぐに切り離す必要があります。オプション RUN{fail_event_on_error} が指定され、実行されたプログラムがゼロ以外の値を返す場合、イベントは失敗としてマークされ、後で処理できるようになります。

スクリプトを正しく理解していれば、特定の X 接続で長時間実行される何かを開始または停止したいのですね。これは、タスクを 2 つのタスクに分割した場合にのみ機能します。つまり、対応する X サーバーの起動時に開始される何らかのデーモンを用意し (認証クッキーを取得するには、X サーバーを起動する必要があります)、次に、udev 実行スクリプトでデーモンに、サービスの開始や停止などの特定の操作を実行するように指示します。

この方法では、udev 実行スクリプトはすぐに終了できます。

systemdを使用している場合は、udevルールからユニットを開始することもできます(例を参照)。ここ)。

編集

を使用する場合は、デーモン上で実行されるtty1ログイン サービス ( getty)を変更することを検討してください。これにより、スクリプトからトリガーできるようになります。tty1

例えばここ実行可能な方法の詳細については、必要に応じて変更してください。

関連情報