Rustでwho(FFIとlibcを試してみる)

# 目的
RustでFFIを試してみたかったので、少ないコード量で実装する
# このページのスコープ
## スコープ対象内
* RustでFFIを使う
* libcを使う

## スコープ対象外
* 効率が良かったり、実行時間が短いコードを書く
* エラーハンドリングなどをしっかり実装した堅牢なコードを書く

# 題材
whoコマンド
# 環境
ubuntu16.04(環境が違うとコードも変わるので注意)
# 仕様
手元の環境ではwhoをそのまま使うとこんな感じの出力がされる

```
% who
bonchi     tty7         2017-01-22 12:32 (:0)
bonchi     pts/20       2017-01-22 12:36 (tmux(4371).%0)
```
# 実装
## C実装
* whoコマンドはutmpファイルを読み込んで、その中身を表示している
* utmpファイルはutmpエントリの構造体の連続になっている
* utmpエントリは`utmp.h`ファイルで`struct utmp`として定義されている
* `utmp.h`は`/usr/include/utmp.h`にある

`/usr/include/utmp.h`を覗くと、以下のようになっている

```/usr/include/utmp.h
/* Get system dependent values and data structures.  */
#include <bits/utmp.h> 
```
ので、今度は`/usr/include/x86_64-linux-gnu/bits/utmp.h`を見てみる

``` C:/usr/include/x86_64-linux-gnu/bits/utmp.h
/* The structure describing an entry in the user accounting database.  */
#define UT_LINESIZE     32 
#define UT_NAMESIZE     32 
#define UT_HOSTSIZE     256

struct utmp
{
  short int ut_type;            /* Type of login.  */
  pid_t ut_pid;                 /* Process ID of login process.  */
  char ut_line[UT_LINESIZE];    /* Devicename.  */
  char ut_id[4];                /* Inittab ID.  */
  char ut_user[UT_NAMESIZE];    /* Username.  */
  char ut_host[UT_HOSTSIZE];    /* Hostname for remote login.  */
  struct exit_status ut_exit;   /* Exit status of a process marked as DEAD_PROCESS.  */
/* The ut_session and ut_tv fields must be the same size when compiled 32- and 64-bit.  This allows data files and shared memory to be shared between 32- and 64-bit applications.  */
  #ifdef __WORDSIZE_TIME64_COMPAT32
    int32_t ut_session;           /* Session ID, used for windowing.  */
    struct
    {
      int32_t tv_sec;             /* Seconds.  */
      int32_t tv_usec;            /* Microseconds.  */
    } ut_tv;                      /* Time entry was made.  */
  #else
    long int ut_session;          /* Session ID, used for windowing.  */
    struct timeval ut_tv;         /* Time entry was made.  */
  #endif

  int32_t ut_addr_v6[4];        /* Internet address of remote host.  */
  char __glibc_reserved[20];            /* Reserved for future use.  */
};

/* 略 */

/* The structure describing the status of a terminated process.  This type is used in `struct utmp' below.  */
struct exit_status
{
  short int e_termination;    /* Process termination status.  */
  short int e_exit;           /* Process exit status.  */
};
```
私の環境では`__WORDSIZE_TIME64_COMPAT32`は`true`
一番単純なC実装はutmpファイルからutmpエントリをひとつずつ読み込んでそこから必要な情報を出力してやればよい

```c:simple_who.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <utmp.h>

int main() {
  struct utmp current_record;
  int utmpfd;
  int reclen = sizeof(current_record);
 
  if ((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1) {
    exit(1);
  }
 
  while (read(utmpfd, &current_record, reclen) == reclen)
    /* current_recordから必要な情報を出力 */
    close(utmpfd);
    return 0;
  }
}            
```


## Rust実装
Rustでwhoを実装するにもC互換な`struct utmp`とそれに関連する他のstruct(`ut_tv`, `exit_status`)と、定数を定義してutmpファイルからutmpエントリをひとつずつ読み込んでそこから必要な情報を出力してやれば良い
### 定数
``` rust:rust_who.rs
pub const UT_LINESIZE: usize = 32;
pub const UT_NAMESIZE: usize = 32;
pub const UT_HOSTSIZE: usize = 256;
static UTMP_FILE_PATH: &'static str = "/var/run/utmp";
```
* `UTMP_FILE_PATH`はutmpファイルの場所を表しており、`/usr/include/paths.h`に`_PATH_UTMP`として定義されていた

### struct
```rust:rust_who.rs
extern crate libc;
use libc::{c_short, pid_t, c_char, int32_t, c_int, size_t, c_void};

#[repr(C)]
pub struct exit_status {
    pub e_termination: c_short,
    pub e_exit: c_short,
}

#[repr(C)]
pub struct ut_tv {
    pub tv_sec: int32_t,
    pub tv_usec: int32_t,
}

#[repr(C)]
pub struct utmp {
    pub ut_type: c_short,
    pub ut_pid: pid_t,
    pub ut_line: [c_char; UT_LINESIZE],
    pub ut_id: [c_char; 4],
    pub ut_user: [c_char; UT_NAMESIZE],
    pub ut_host: [c_char; UT_HOSTSIZE],
    pub ut_exit: exit_status,
    pub ut_session: int32_t,
    pub ut_tv: ut_tv,
    pub ut_addr_v6: [int32_t; 4],
    pub __glibc_reserved: [c_char; 20],
}

impl Default for exit_status {
    fn default() -> exit_status {
        exit_status {
            e_termination: 0,
            e_exit: 0,
        }
    }
}

impl Default for ut_tv {
    fn default() -> ut_tv {
        ut_tv {
            tv_sec: 0,
            tv_usec: 0,
        }
    }
}

impl Default for utmp {
    fn default() -> utmp {
        utmp {
            ut_type: 0,
            ut_pid: 0,
            ut_line: [0; UT_LINESIZE],
            ut_id: [0; 4],
            ut_user: [0; UT_NAMESIZE],
            ut_host: [0; UT_HOSTSIZE],
            ut_exit: Default::default(),
            ut_session: 0,
            ut_tv: Default::default(),
            ut_addr_v6: [0; 4],
            __glibc_reserved: [0; 20],
        }
    }
}
```
* Cのstructを`#[repr(C)]`で宣言している
  * https://doc.rust-lang.org/nomicon/other-reprs.html#reprc
  * https://doc.rust-lang.org/book/ffi.html#interoperability-with-foreign-code
* structのメンバーはC互換の型にしたいのでlibc crateを使っている
    * https://github.com/rust-lang/libc
* `Defalut`trateの実装は必須ではないのですが、rustでは変数の宣言時に初期化をする必要があり、いちいち値を入れるのが面倒なので簡単に0で初期化できるようにしておく

### 関数
```rust:rust_who.rs
extern "C" {
    pub fn read(fd: c_int, buf: *mut c_void, count: size_t) -> usize;
}

fn main() {
    let utmp_file = File::open(UTMP_FILE_PATH).unwrap();
    let mut utmp_struct: utmp = Default::default();
    let buffer: *mut c_void = &mut utmp_struct as *mut _ as *mut c_void;
    unsafe {
        while read(utmp_file.as_raw_fd(), buffer, mem::size_of::<utmp>()) != 0 {
            show_info(&utmp_struct);
        }
    }
}

fn show_info(utmp_struct: &utmp) {
    print!("{} ",
           String::from_utf8((utmp_struct.ut_user.iter().map(|&x| x as u8).collect())).unwrap());
    print!("{} ",
           String::from_utf8((utmp_struct.ut_line.iter().map(|&x| x as u8).collect())).unwrap());
    print!("{} ", utmp_struct.ut_tv.tv_sec);
    print!("({}) ",
           String::from_utf8((utmp_struct.ut_host.iter().map(|&x| x as u8).collect())).unwrap());
    println!("");
}
```
* `extern "C"`ブロックで囲むことでCのread関数を呼べるようにしている
    * https://doc.rust-lang.org/book/ffi.html#foreign-calling-conventions
    * 呼び出し時はC関数の呼び出しなので`unsafe`ブロックで囲っている
    * Cのread関数では第一引数にファイルディスクリプタを渡す
        * [Rustでファイルディスクリプタを取得する](https://doc.rust-lang.org/std/os/unix/io/trait.AsRawFd.html)
    * Cのread関数では第二引数に`void`を渡す
        * `void`を渡すところは`libc`で定義されている`c_void`を渡す
        * 読み取った情報を`struct utmp`に入れて欲しいので、`struct utmp`のポインタを渡したい`struct utmp` -> c_voidへの変換は[ここ](http://stackoverflow.com/a/24191977)を参考にした
    * rustは変数の宣言時に初期化もしなくてはならないので、先ほど実装した`Default` trateを使っている
* `show_info`関数は`struct utmp`に埋め込まれた情報からwhoに必要なものだけを出力している
    * `struct utmp`に埋め込まれた各メンバーの値を単純に見ても単なる数字の配列にしか見えない

# 結果
```
% cargo run
reboot ~ 1485055829 (4.4.0-59-generic) 
runlevel ~ 1485055839 (4.4.0-59-generic) 
LOGIN tty1 1485055839 () 
bonchi tty7 1485055935 (:0) 
bonchi pts/20 1485056209 (tmux(4371).%0) 
```
それっぽく出力された。
しかし、余計な行がいくつか増えているのと、ログイン時刻がUNIX timeになっている
余計な行は`struct utmp`のメンバーの`ut_type`を見て`ut_tipe`が`USER_PROCESS`でないものを飛ばせば良い。UNIX timeは適当に変換すれば良い
ソースはこちら
https://github.com/bon-chi/rust_who

# 備考
このソースは特定の環境でしか動作しない(Ubuntu16.04)
というのも`utmp.h`は端末によって中身が違うからだ。例えばOS Xの`utmp.h`を覗いてみると

```c:usr/include/utmp.h
/*
 * This header file is DEPRECATED and only provided for compatibility
 * with previous releases of Mac OS X.  Use of these structures, especially
 * in 64-bit computing, may corrupt the utmp, wtmp and lastlog files.
 *
 * Use the utmpx APIs instead.
 */

/*略*/

struct utmp {
        char    ut_line[UT_LINESIZE];
        char    ut_name[UT_NAMESIZE];
        char    ut_host[UT_HOSTSIZE];
        long    ut_time;
}
```

となっているし、実際に先ほどのコードを実行すると、

```
00 0 (
)
  0 ()
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: FromUtf8Error { bytes: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 213, 64, 103, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], error: Utf8Error { valid_up_to: 84 } }', ../src/libcore/result.rs:837
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```

と出力される

# 参考
http://siciarz.net/ffi-rust-writing-bindings-libcpuid/
http://stackoverflow.com/questions/24191249/working-with-c-void-in-an-ffi#comment37351252_24191977
https://doc.rust-lang.org/nomicon/other-reprs.html#reprc
https://doc.rust-lang.org/book/ffi.html