こういう話題を見て、そもそも読み出すたびに内容が変化するようなファイル(としてふるまう何か)を作ることはできるだろうか?ということを考えた。
linuxで時間帯によって設定を変える方法の定石ってなんだろう やっぱタイマーで設定ファイル書き換え?なんかもっとスマートな方法ありそうな気するけど
— hisaket (@hisaketM) 2022年9月5日
これ考えてて思ったんですけど、ファイルがオープンされる度に指定したコマンドが実行されて、コマンドの出力結果がファイルの内容になるような特殊なファイルが作れたら便利そうじゃないですか?そういうFUSEないのかな
— hisaket (@hisaketM) 2022年9月5日
リプライなどでは、symlinkを切り替える案や、デバイスファイルを作るという案が登場していて面白い。やろうと思えばなんとかなるのがUNIX wayの面白さだと思う。
名前付きパイプで行けるんちゃう?作戦
そもそも、ファイルとして振る舞うがプログラムが内容を注入できるものとして、名前付きパイプというものがある。
% mkfifo testpipe % echo "foo" > testpipe [block until read]
% cat testpipe [block until writing to foo] foo
これをうまく使って、複数回読み出せて、なおかつ内容も変化するような名前付きパイプは可能だろうか?
\d
する作戦
よく、入力の終端文字として^D
が用いられる。じゃあ、パイプに入力する文字列に\d
を埋め込んでおけば、勝手に読み込んだ側はそこがファイルの終端だと勘違いしてくれるんじゃないの?と直感的に思ったので試してみた:
% echo "foo\dbar\dbuzz" > testpipe
% cat testpipe foo\dbar\dbuzz
普通に出力されてしまった。そもそも、Ctrl+DでEOFが送信されるというのは端末側で定義されている動作。
Stack Overflowで似たような事を質問している人がいて、直接EOFを送信することはできないらしい:
EOF
文字やそれに類似したイベントがpipeを通過して届くことはない- pipeを
read
して発生しているブロックが解除されるためには、そのパイプの開いているハンドルが全て閉じる必要がある
そもそもパイプって何?
そもそもパイプはファイルとは異なり、非直感的な振舞いをするようだ。パイプを経由した読み書きは完全に同期的に行われ、途中でどこかに蓄積されていくということはない。
パイプについて書かれたドキュメントpipe(7)
をおさらいしてみよう。
- パイプはファイルシステムのファイルのように読み書きできる
- プロセスが空のパイプから読み出そうとした場合、データが到着するまでブロックする
- プロセスがfull状態のパイプに書き込もうとした場合もブロックする
- パイプはバイトストリームである: メッセージ境界は存在しない
- パイプの書き込み側( write end )を向いた全てのファイルディスクリプタが閉じたとき、パイプの読み出し(
read
)は0を返す(いわゆる「EOFを見た」状態になる) - パイプの読み込み側( read end )を向いた全てのファイルディスクリプタが閉じたとき、パイプへの書き込みは
SIGPIPE
を引き起こす- fork/pipeを使うプロセスが、未使用のファイルディスクリプタを即座に閉じなければならないのはこのためである。適切なEOF/SIGPIPEを発生させるためである。
このため、先程の「単一のパイプをうまく読み込ませる」作戦を行うならば、「パイプに書き込む」「パイプを読ませる」「パイプを読んだことを検知して書き込み側のファイルディスクリプタを閉じてEOFを発生させる」「readがreturnしたことを確認して再度パイプを書き込みモードで開く」という器用な行いをする必要があることがわかる。
そして、この作戦は「同時に読み書きされる」という状況が発生すると容易に崩壊してしまう・・・。
正直、プロセスやカーネルモジュールが居座るより小さなテキストが tmpfs に居座る方がオーバーヘッド少なそうなので、 (atomicity の保証には気を使いたいが) タイマーで定期的に tmpfs にファイル出すのが早いと思う
— らりお・ザ・.*🈗然㊌㋞㋰㋷㋓ (@lo48576) 2022年9月5日
たぶんこれが一番早いとおもいます・・・