# 帳票設計書 16-IO ファイル書き込み

## 概要

本ドキュメントは、mrubyのIO/Fileクラスによるファイルへのデータ書き込み機能の設計仕様を記述する。IO#write、IO#puts、IO#print等のメソッドによりテキスト・バイナリデータのファイル書き込みを行う。

### 本帳票の処理概要

mruby-io gemが提供するIO/Fileクラスは、ファイルへのデータ書き込み機能を提供する。write、puts、print、putc、syswrite、pwrite等の複数の書き込みメソッドをサポートし、テキストおよびバイナリデータの出力が可能である。

**業務上の目的・背景**：ファイルへのデータ出力はプログラミングにおける最も基本的なI/O操作の一つである。mrubyにおいてもCRubyと互換性のあるIO/Fileクラスによる書き込みインターフェースを提供することで、データの永続化、ログ出力、データ交換等の基本的なファイル操作を可能にしている。ISO規格(ISO 15.2.20.5.20 IO#write)にも定義されている標準的な機能である。

**帳票の利用シーン**：mrubyスクリプトからファイルにデータを書き込む場面で使用される。設定ファイルの生成、ログの保存、処理結果のCSV/テキスト出力、バイナリデータの保存等の用途がある。

**主要な出力内容**：
1. IO#write: 指定バイト数のデータ書き込み(書き込みバイト数を返却)
2. IO#puts: 改行付き文字列の書き込み(Array再帰展開対応)
3. IO#print: 引数の文字列変換結果の連結書き込み
4. IO#putc: 1文字/1バイトの書き込み
5. IO#syswrite: 低レベルシステムコール直接呼び出し
6. IO#pwrite: 指定オフセット位置への書き込み(POSIX pwrite)

**帳票の出力タイミング**：mrubyスクリプト内でIO/Fileオブジェクトの書き込みメソッドが呼び出された時点でファイルに書き込まれる。

**帳票の利用者**：mrubyスクリプトの開発者

## 帳票種別

ファイル出力(テキスト/バイナリ)

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | N/A | N/A | Rubyスクリプト内でIO/File書き込みメソッドを呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | 任意(テキスト/バイナリ) |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | File.open引数で指定 |
| 出力方法 | ファイルシステムへの直接書き込み(writeシステムコール) |
| 文字コード | 入力データ依存(バイト列としてそのまま出力) |

### PDF固有設定

N/A

### Excel固有設定

N/A

## 帳票レイアウト

### レイアウト概要

ファイル書き込みは任意のデータ形式をサポートし、固定レイアウトを持たない。

```
┌─────────────────────────────────────┐
│ [write] 引数データのバイト列         │
│ [puts]  引数+改行文字               │
│ [print] 引数の連結文字列            │
│ [putc]  1文字/1バイト               │
└─────────────────────────────────────┘
```

### ヘッダー部

N/A

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | 書き込みデータ | メソッド引数のデータ | mrb_value引数 | バイト列/文字列 | 可変 |

### フッター部

N/A

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| mruby-io gem | mruby-io gemがビルドに含まれていること | Yes |
| MRB_NO_STDIO未定義 | MRB_NO_STDIOが定義されている場合はビルドエラー | Yes |
| 書き込みモード | IOオブジェクトがwritable属性を持つこと | Yes |
| ファイルオープン済み | fd >= 0 であること(クローズ済みでないこと) | Yes |

### ソート順

N/A

### 改ページ条件

N/A

## データベース参照仕様

### 参照テーブル一覧

N/A

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 書き込みバイト数 | write()システムコールの返り値の累計 | N/A | IO#writeの返り値として使用 |
| putcバイト値 | mrb_integer(c) & 0xff | N/A | Integer引数時のバイト変換(mod 256) |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[IO/File書き込みメソッド呼び出し] --> B[io_get_write_fptr でfptr取得]
    B --> C[io_get_write_fd でFD取得]
    C --> D[io_prepare_write でバッファ調整]
    D --> E{メソッド種別}
    E -->|write| F[fd_write で全バイト書き込み]
    E -->|puts| G[io_puts_str/io_puts_ary で改行付き書き込み]
    E -->|print| H[fd_write で引数連結書き込み]
    E -->|putc| I[1バイト書き込み]
    E -->|syswrite| J[io_write_common でwrite直接呼び出し]
    F --> K[書き込みバイト数を返却]
    G --> L[nil を返却]
    H --> L
    I --> M[引数をそのまま返却]
    J --> K
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| IOError(未初期化) | IOオブジェクトが初期化されていない | "uninitialized stream" | IO.newまたはFile.openで初期化 |
| IOError(クローズ済) | IOオブジェクトがclose済み(fd < 0) | "closed stream" | ファイルを再オープン |
| IOError(書き込み不可) | 読み取り専用モードでオープンされたIOに書き込み | "not opened for writing" | 書き込みモード("w"/"a"/"r+")でオープン |
| SystemCallError | write()システムコールが-1を返した場合 | "syswrite" (mrb_sys_fail) | ディスク容量、パーミッション等を確認 |
| EINTR | write中に割り込みが発生した場合 | (自動リトライ) | fd_write内で自動的に再試行される |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 制限なし(メモリおよびディスク容量に依存) |
| 目標出力時間 | writeシステムコール依存 |
| 同時出力数上限 | ファイルディスクリプタ上限に依存 |

## セキュリティ考慮事項

- ファイル書き込みはOSのファイルパーミッションに従う
- パス操作による意図しないファイルへの書き込み(パストラバーサル)に注意
- O_CREATフラグ使用時のumask設定に注意
- 書き込み先ファイルの所有権・パーミッション確認が必要

## 備考

- mrb_io構造体はfd(読み取り用)とfd2(書き込み用)の2つのファイルディスクリプタを持つ。fd2が-1でない場合はfd2が書き込み先となる(パイプ等で使用)
- io_prepare_write関数は読み取りバッファの状態を調整してから書き込みを行う。バッファにデータが残っている場合はlseekでカーソルを戻す
- fd_write関数はEINTR(割り込み)を自動的にリトライする実装になっている
- pread/pwriteはMRB_USE_IO_PREAD_PWRITE定義時のみ利用可能(Unix/macOS系)
- IO#<<演算子もサポートされている(io_lshift関数)

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

IO関連の中核データ構造(mrb_io構造体とmrb_io_buf構造体)を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | io.h | `mrbgems/mruby-io/include/mruby/io.h` | mrb_io構造体(行34-46) - fd/fd2/pid/readable/writable等のフィールド |
| 1-2 | io.h | `mrbgems/mruby-io/include/mruby/io.h` | mrb_io_buf構造体(行28-32) - MRB_IO_BUF_SIZE(4096)のバッファ |
| 1-3 | io.h | `mrbgems/mruby-io/include/mruby/io.h` | MRB_O_* フラグ定義(行48-66) |

**読解のコツ**: mrb_io構造体のfd/fd2の使い分けが重要。通常はfdが読み書き両用、fd2はパイプ等で書き込み専用FDが別途必要な場合に使用される。ビットフィールドでreadable/writable等の状態フラグを管理している。

#### Step 2: エントリーポイントを理解する

IO書き込みメソッドの登録と基盤関数を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | io.c | `mrbgems/mruby-io/src/io.c` | メソッド登録テーブル(行2228-2232) - write/puts/print/putcの登録 |
| 2-2 | io.c | `mrbgems/mruby-io/src/io.c` | io_get_open_fptr(行84-94) - IOオブジェクトの有効性チェック |
| 2-3 | io.c | `mrbgems/mruby-io/src/io.c` | io_modestr_to_flags(行130-171) - オープンモード文字列のフラグ変換 |

**主要処理フロー**:
1. **行2228-2232**: io_write(write), io_puts(puts), io_print(print), io_putc(putc)の登録
2. **行84-94**: io_get_open_fptr - NULLチェック、fd<0チェック(クローズ済みチェック)
3. **行130-171**: "r"/"w"/"a"/"+"/b"等のモード文字列をO_*フラグに変換

#### Step 3: 書き込みコア処理を理解する

fd_writeとio_prepare_write関数の実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | io.c | `mrbgems/mruby-io/src/io.c` | fd_write(行914-934) - EINTR対応のwrite実装 |
| 3-2 | io.c | `mrbgems/mruby-io/src/io.c` | io_prepare_write(行936-952) - バッファ状態の調整 |

**主要処理フロー**:
- **行914-934 (fd_write)**: mrb_obj_as_stringで文字列変換後、writeシステムコールをEINTRリトライ付きでループ実行
- **行936-952 (io_prepare_write)**: 読み取りバッファにデータが残っている場合、lseekでカーソルを戻してバッファをクリア

#### Step 4: 各書き込みメソッドの実装を理解する

io_write、io_puts、io_print、io_putcの個別実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | io.c | `mrbgems/mruby-io/src/io.c` | io_write(行954-976) - 可変長引数対応のwrite |
| 4-2 | io.c | `mrbgems/mruby-io/src/io.c` | io_puts(行1020-1052) - 改行付き出力 |
| 4-3 | io.c | `mrbgems/mruby-io/src/io.c` | io_print(行1062-1082) - 引数連結出力 |
| 4-4 | io.c | `mrbgems/mruby-io/src/io.c` | io_putc(行1092-1119) - 1文字/1バイト出力 |

**主要処理フロー**:
- **行954-976 (io_write)**: 引数1個の場合は最適化パス、複数の場合はループでfd_writeを呼び出し
- **行1020-1052 (io_puts)**: 引数0個なら改行のみ、Array引数はio_puts_aryで再帰展開
- **行1062-1082 (io_print)**: 各引数をmrb_obj_as_stringでString化してfd_write
- **行1092-1119 (io_putc)**: Integer引数は& 0xffでバイト値に変換、String引数は先頭文字を出力

### プログラム呼び出し階層図

```
IO#write (Ruby側)
    |
    +-- io_write (io.c:954)
           +-- io_get_write_fptr → mrb_io構造体取得
           +-- io_get_write_fd → fd2 >= 0 ? fd2 : fd
           +-- io_prepare_write → バッファ調整(lseek)
           +-- fd_write (io.c:914)
                  +-- mrb_obj_as_string (to_s変換)
                  +-- write() ループ (EINTR対応)

IO#puts (Ruby側)
    |
    +-- io_puts (io.c:1020)
           +-- io_get_write_fptr / io_get_write_fd / io_prepare_write
           +-- io_puts_str (io.c:978)  ※文字列引数
           |      +-- fd_write(str)
           |      +-- [末尾改行なし] fd_write("\n")
           +-- io_puts_ary (io.c:996)  ※Array引数(再帰)
                  +-- io_puts_str / io_puts_ary

IO#print (Ruby側)
    |
    +-- io_print (io.c:1062)
           +-- io_get_write_fptr / io_get_write_fd / io_prepare_write
           +-- mrb_obj_as_string → fd_write (各引数)

IO#putc (Ruby側)
    |
    +-- io_putc (io.c:1092)
           +-- io_get_write_fptr / io_get_write_fd / io_prepare_write
           +-- [Integer] write(&byte, 1)
           +-- [String] fd_write(先頭文字)
```

### データフロー図

```
[入力]                      [処理]                        [出力]

Ruby引数            io_get_write_fptr()
(mrb_value) ──────> (書き込み可能チェック)
                          |
                    io_prepare_write()
                    (バッファ調整)
                          |
                    mrb_obj_as_string()
                    (文字列変換)
                          |
                    fd_write() ──────> write() ──────> ファイル
                    (EINTR対応ループ)   (syscall)      (ディスク)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| io.c | `mrbgems/mruby-io/src/io.c` | ソース | IO書き込みメソッド全体の実装 |
| io.h | `mrbgems/mruby-io/include/mruby/io.h` | ヘッダ | mrb_io構造体、mrb_io_buf構造体、MRB_O_*フラグ定義 |
| mruby_io_gem.c | `mrbgems/mruby-io/src/mruby_io_gem.c` | ソース | IO gem初期化(mrb_init_io呼び出し) |
| io_hal.h | `mrbgems/mruby-io/src/io_hal.h` | ヘッダ | HAL(Hardware Abstraction Layer)定義 |
| mrbgem.rake | `mrbgems/mruby-io/mrbgem.rake` | 設定 | gem仕様、HAVE_MRUBY_IO_GEMマクロ定義 |
