Rustでnamed pipeのバックエンドを書いてみる

Rustでnamed pipeのバックエンドを書いてみる

1Passwordのシークレット管理で使われているfifo (named pipe)について、Rustでバックエンドの常駐プロセスを実装してみました。ブロッキングI/Oやメタデータ判定などの実装ポイントを紹介します。
2026.09.18

こんばんは、情報システム室の夏目です。

各種シークレットをmacのローカル環境で使用するとき、 1Password Environments.envマウント を使用しています。

.envマウントではfifo (named pipe)として.envを置きます。

$  file tmp-values.env
tmp-values.env: fifo (named pipe)

.envマウントでは読み取りリクエストがあったときに1Passwordのデスクトップアプリで認証を通してから、1Passwordのアプリが登録している値を返すものです。
テキストファイルとして置くわけではないので間違えてGit管理に追加してしまうということを避けられます。

fifo (named pipe)では裏側に常駐プロセスが必要で、この常駐プロセスをRustで作ってみます。

0. 環境

  • macOS Tahoe 26.6.2
  • rustc 1.98.1 (48a229cea 2026-09-01)

1. Rustのプロジェクトを作成する

cargoコマンドでRustのプロジェクトを作ります。

$ cargo new trial-named-pipe
    Creating binary (application) `trial-named-pipe` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

$ cd trial-named-pipe
$ eza -lT
drwxr-xr-x@  - natsume.yuta 18 Sep 15:57 .
.rw-r--r--@ 87 natsume.yuta 18 Sep 15:57 ├── Cargo.toml
drwxr-xr-x@  - natsume.yuta 18 Sep 15:57 └── src
.rw-r--r--@ 45 natsume.yuta 18 Sep 15:57     └── main.rs

2. main.rs を編集する

main.rsを編集して、fifo (named pipe)用の常駐プロセスを実装します。
ここのコードでは named_pipe という名前の fifo (named pipe) を作成しています。

use std::fs::OpenOptions;
use std::io::{Error, ErrorKind, Write};
use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

fn main() -> std::io::Result<()> {
    let fifo_path = Path::new("named_pipe");

    ensure_fifo(fifo_path)?;

    println!(
        "{}: Waiting for reader on {} ...",
        get_unixtime_ms(),
        fifo_path.display()
    );

    loop {
        let mut pipe = OpenOptions::new().write(true).open(fifo_path)?;

        println!(
            "{}: Reader connected. Writing response...",
            get_unixtime_ms()
        );

        pipe.write_all(format!("read! ({})", get_unixtime_ms()).as_ref())?;
        pipe.flush()?;
        drop(pipe);

        std::thread::sleep(Duration::from_millis(100));

        println!("{}: Done. Waiting for next reader...\n", get_unixtime_ms());
    }
}

// ミリ秒単位のUnixTimeを算出する関数
fn get_unixtime_ms() -> u128 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
}

fn ensure_fifo(path: &Path) -> std::io::Result<()> {
    if path.exists() {
        let metadata = std::fs::metadata(path)?;

        if metadata.file_type().is_fifo() {
            return Ok(());
        }

        return Err(Error::new(
            ErrorKind::AlreadyExists,
            format!("{} exists but is not a FIFO", path.display()),
        ));
    }

    let status = std::process::Command::new("mkfifo").arg(path).status()?;

    if !status.success() {
        return Err(Error::new(
            ErrorKind::Other,
            "failed to create FIFO with mkfifo",
        ));
    }

    Ok(())
}

fn ensure_fifo(path: &Path) -> std::io::Result<()>

これは fifo (named pipe) があればそれを使い、なければ作成するコードです。

std::os::unix::fs::FileTypeExt というtraitを使って、メタデータのファイルタイプでfifoかどうかを判定できるようにしています。

loop { ... }

常駐プロセスの本体です。
loopでくくっているので無限ループとして常駐プロセスとしています。

let mut pipe = OpenOptions::new().write(true).open(fifo_path)?;

ここの部分でreaderが読み込みに来るまで待ち受けています。
ブロッキングになっているのでカーネルないでsleepし、CPUをほぼ使用していません。

pipe.write_all(format!("read! ({})", get_unixtime_ms()).as_ref())?;
pipe.flush()?;
drop(pipe);

readerに値を返すコードです。
pipe.write_all()pipe.flush() でreaderに返す値を書き込み、 drop() で明示的に閉じています。

std::thread::sleep(Duration::from_millis(100));

これは常駐プロセスのループが早すぎて、readerがEOFを検知する前に次のループで書き込みが行われるのを防ぐためのウェイトです。
これを入れていないとき、 bat (cat) コマンドで読み取りを行うと複数回書き込みが行われました。
(Pythonのコードで読み取りを行ったときは複数回書き込みが行われなかったのですが)

$ bat named_pipe
─────┬─────────────────────────────────────────────────────────────────────────────────────────
 File: named_pipe
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 read! (2026-09-18T06:38:49.805761+00:00)
   2 read! (2026-09-18T06:38:49.809518+00:00)
   3 read! (2026-09-18T06:38:49.813412+00:00)
─────┴─────────────────────────────────────────────────────────────────────────────────────────

3. 実行してみる

常駐プロセス側

$ cargo run
   Compiling trial-named-pipe v0.1.0 (/Users/natsume.yuta/spaces/work/blog/020_named_pipe_by_rust/trial-named-pipe)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.85s
     Running `target/debug/trial-named-pipe`
1789717503512: Waiting for reader on named_pipe ...
1789717514217: Reader connected. Writing response...
1789717514322: Done. Waiting for next reader...

1789717570866: Reader connected. Writing response...
1789717570971: Done. Waiting for next reader...

1789717596567: Reader connected. Writing response...
1789717596668: Done. Waiting for next reader...

$ bat named_pipe
─────┬─────────────────────────────────────────────────────────────────────────────────────────
 File: named_pipe
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 read! (1789717514217)
─────┴─────────────────────────────────────────────────────────────────────────────────────────

$ bat named_pipe
─────┬─────────────────────────────────────────────────────────────────────────────────────────
 File: named_pipe
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 read! (1789717570866)
─────┴─────────────────────────────────────────────────────────────────────────────────────────

$  bat named_pipe
─────┬─────────────────────────────────────────────────────────────────────────────────────────
 File: named_pipe
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 read! (1789717596567)
─────┴─────────────────────────────────────────────────────────────────────────────────────────

きちんと動きました。

まとめ

以上、fifo (named pipe)のバックエンドをRustで作成してみました。

あくまでプロトタイプレベルの実装なので実際のプロダクトまでするには、今回ウェイトで対応したreaderの読み込み完了をきちんと追跡するようにしたりと色々手を入れる必要があります。

何かのお役に立てたら幸いです。

この記事をシェアする

関連記事