Rust自作Shellでの多段パイプの実装

7 min読了の目安(約7000字TECH技術記事

https://zenn.dev/garebare/articles/a463257c447fa9 の記事の続きみたいな感じです。

使うライブラリです。

[dependencies]
nix = "0.19.0"

https://github.com/nix-rust/nix
このライブラリでシステムコールを扱います。

コマンド実行部分

コマンドの実行部分はこんな感じになっています。

fn sh_launch(&mut self, command: &parser::parser::CommandParse) -> Result<(), String> {
        let is_empty = !self.is_empty_pipes();
        match command.get_pipe() {
            Some(_) => match pipe() {
                Ok(pipe) => {
                    self.push_pipe(pipe);
                }
                Err(_) => {
                    return Err(format!("Pipe error"));
                }
            },
            None => {}
        }
        //プロセスの生成
        match unsafe { fork() } {
            //親プロセス
            Ok(ForkResult::Parent { child, .. }) => {
                self.push_process(child);
                if is_empty {
                    match self.pearent_connect_end() {
                        Ok(_) => {}
                        Err(e) => {
                            return Err(e);
                        }
                    }
                }
                match command.get_pipe() {
                    Some(pipe) => match self.sh_launch(&pipe) {
                        Ok(()) => {}
                        Err(e) => {
                            return Err(e);
                        }
                    },
                    None => {}
                }
            }
            //子プロセス
            Ok(ForkResult::Child) => unsafe {
		//追加分
		//一番最初のパイプ
                if command.get_pipe().is_some() && self.len_pipes() == 0 {
                    match self.pipe_first_connect() {
                        Ok(_) => {}
                        Err(e) => {
                            println!("{}", e);
                            exit(-1);
                        }
                    }
                } else if !self.is_empty_pipes() && !command.get_pipe().is_some() {
                    //一番最後のパイプ
		    match self.pipe_end_connect() {
                        Ok(_) => {}
                        Err(e) => {
                            println!("{}", e);
                            exit(-1);
                        }
                    }
                } else if !self.is_empty_pipes() && command.get_pipe().is_some() {
                    //中間のパイプ
		    match self.pipe_route_connect() {
                        Ok(_) => {}
                        Err(e) => {
                            println!("{}", e);
                            exit(-1);
                        }
                    }
                }

                let cstring = CString::new(command.get_command()).expect("CString::new failed");
                let cstr = CStr::from_bytes_with_nul_unchecked(cstring.to_bytes_with_nul());
                let mut argv: Vec<CString> = Vec::new();
                self.push_argv(&mut argv);
                let result = execvp(cstr, &argv);
                match result {
                    Ok(_) => {
                        exit(0);
                    }

                    Err(_) => {
                        println!("{}: command not found", command.get_command());
                        exit(-1);
                    }
                }
            },

            Err(_) => {
                return Err(format!("Fork Failed"));
            }
        }
        return Ok(());
    }

前の記事の実行部分に子プロセスの処理にパイプの位置の判定を追加しています。
プロセスを生成をする前にパイプ生成して配列に保持しています。
使い終わったパイプを閉じるのに子プロセスのpidも保持しています。
そして再帰的に親プロセスで次のコマンドを実行しています。

多段パイプ

最初のコマンドだった場合は保持しているパイプの配列から最後尾を取り出し標準出力をパイプの入り口へつなげます。
最後のコマンドも最後尾から取り出し標準入力をパイプの出口へつなげています。
そして中間のコマンドは最後尾から1つ前の要素を取り出し標準入力を出口へつなげ、最後尾の要素の方は標準出力を入り口につないでいます。
そうすると全てのコマンドをパイプで繋げることができます。

use nix::unistd::*;

use super::process::Process;

impl Process {
//最初のコマンド
  pub(crate) fn pipe_first_connect(&self) -> Result<(), String> {
    match self.get_pipe(self.len_pipes()) {
      Some(pipes) => {
        match dup2(pipes.1, 1) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe first dup2 error"));
          }
        }

        match self.close_pipe(pipes) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe first close error"));
          }
        }
      }
      None => {
        return Err(format!("pipe first missing"));
      }
    }

    return Ok(());
  }
//最後のコマンド
  pub(crate) fn pipe_end_connect(&self) -> Result<(), String> {
    match self.get_pipe(self.len_pipes()) {
      Some(pipes) => {
        match dup2(pipes.0, 0) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe close dup2 error"));
          }
        }

        match self.close_pipe(pipes) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe end close error"));
          }
        }
      }

      None => {
        return Err(format!("pipe end missing"));
      }
    }

    return Ok(());
  }
//途中のコマンド
  pub(crate) fn pipe_route_connect(&self) -> Result<(), String> {
    match self.get_pipe(self.len_pipes() - 1) {
      Some(pipes) => {
        match dup2(pipes.0, 0) {
          Ok(_) => {}

          Err(_) => {
            return Err(format!("pipe route dup2 error"));
          }
        }

        match self.close_pipe(pipes) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe route close error"));
          }
        }
      }

      None => {
        return Err(format!("pipe route missinag"));
      }
    }

    match self.get_pipe(self.len_pipes()) {
      Some(pipes) => {
        match dup2(pipes.1, 1) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe route dup2 error"));
          }
        }
        match self.close_pipe(pipes) {
          Ok(_) => {}
          Err(_) => {
            return Err(format!("pipe route close error"));
          }
        }
      }
      None => {
        return Err(format!("pipe route missinag"));
      }
    }

    return Ok(());
  }
//使い終わったパイプを閉じる
  pub(crate) fn pearent_connect_end(&mut self) -> Result<(), String> {
    match self.get_pipe(0) {
      Some(pipes) => match self.close_pipe(pipes) {
        Ok(_) => {}
        Err(_) => {
          return Err(format!("pearent end close error"));
        }
      },
      None => {
        return Err(format!("pipe missing"));
      }
    }

    self.deque_pipe();
    return Ok(());
  }
//パイプを閉じる
  pub(crate) fn close_pipe(&self, pipe: &(i32, i32)) -> Result<(), String> {
    match close(pipe.0) {
      Ok(_) => {}
      Err(_) => {
        return Err(format!("pipe close error"));
      }
    }
    match close(pipe.1) {
      Ok(_) => {}
      Err(_) => {
        return Err(format!("pipe clsoe error"));
      }
    }
    return Ok(());
  }
}

参考記事

https://keiorogiken.wordpress.com/2017/12/15/シェルの多段パイプを自作してみる/
https://www.slideshare.net/yusukesangenya/ss-135407412