# 帳票設計書 17-mailstats - メール統計レポート

## 概要

本ドキュメントは、FreeBSD の `mailstats` コマンドが出力するメール統計レポートの設計仕様を記述する。

### 本帳票の処理概要

mailstats は、sendmailのメール送受信統計情報をレポートするユーティリティである。sendmailの統計ファイルからメーラーごとのメッセージ数とバイト数を読み取り、集計して出力する。

**業務上の目的・背景**：メールサーバーの運用管理において、メール送受信量の把握とメーラー別の利用状況分析のために使用される。メールシステムの容量計画、異常トラフィックの検出、サービスレベルの監視などの課題を解決する。

**帳票の利用シーン**：メールサーバーの定期運用レポート作成、メール送受信量の監視、メーラー別利用状況分析、トラフィック異常の検出などの場面で利用される。

**主要な出力内容**：
1. 統計期間（統計開始時刻）
2. メーラー番号と名前
3. 送信メッセージ数とバイト数
4. 受信メッセージ数とバイト数
5. 拒否・破棄・検疫メッセージ数
6. 合計行（全メーラー合計）
7. 接続統計（from/to/reject）

**帳票の出力タイミング**：管理者がコマンドラインから `mailstats` コマンドを実行した際に標準出力に出力される。

**帳票の利用者**：メールサーバー管理者

## 帳票種別

集計表（メーラー別メール統計）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | コマンドラインインターフェース | N/A | `mailstats` コマンド実行 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（標準出力） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（標準出力） |
| 出力方法 | 標準出力（stdout） |
| 文字コード | ロケール依存 |

## 帳票レイアウト

### レイアウト概要

ヘッダー行、メーラー別データ行、セパレータ行、合計行、接続統計行で構成される。

```
┌──────────────────────────────────────────────────────────────┐
│ Statistics from {start_time}                                  │
│  M   msgsfr  bytes_from   msgsto    bytes_to  msgsrej msgsdis│
│     msgsqur  Mailer                                           │
├──────────────────────────────────────────────────────────────┤
│  0   xxxxx   xxxxxK   xxxxx   xxxxxK   xxxxx  xxxxx  xxxxx  │
│  1   xxxxx   xxxxxK   xxxxx   xxxxxK   xxxxx  xxxxx  xxxxx  │
│  ...                                                          │
├──────────────────────────────────────────────────────────────┤
│ =============================================================│
│  T   xxxxx   xxxxxK   xxxxx   xxxxxK   xxxxx  xxxxx  xxxxx  │
│  C   xxxxx            xxxxx            xxxxx                  │
└──────────────────────────────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 統計開始時刻 | 統計収集開始時刻 | stats.stat_itime | "Statistics from {ctime}" |
| 2 | カラムヘッダー | 各列の見出し | 固定文字列 | "M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis msgsqur Mailer" |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | メーラー番号 | メーラーのインデックス番号 | ループインデックス i | "%2d" | 2文字 |
| 2 | 送信メッセージ数 | 送信されたメッセージ数 | stats.stat_nf[i] | "%8ld" | 8文字 |
| 3 | 送信バイト数 | 送信バイト数（キロバイト） | stats.stat_bf[i] | "%10ldK" | 10文字+K |
| 4 | 受信メッセージ数 | 受信されたメッセージ数 | stats.stat_nt[i] | "%8ld" | 8文字 |
| 5 | 受信バイト数 | 受信バイト数（キロバイト） | stats.stat_bt[i] | "%10ldK" | 10文字+K |
| 6 | 拒否メッセージ数 | 拒否されたメッセージ数 | stats.stat_nr[i] | "%6ld" | 6文字 |
| 7 | 破棄メッセージ数 | 破棄されたメッセージ数 | stats.stat_nd[i] | "%6ld" | 6文字 |
| 8 | 検疫メッセージ数 | 検疫されたメッセージ数 | stats.stat_nq[i] | "%6ld" | 6文字 |
| 9 | メーラー名 | sendmail設定ファイルのメーラー名 | mtable[i] | 文字列 | 最大20文字 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 合計行(T) | 全メーラー合計 | frmsgs/frbytes/tomsgs/tobytes/rejmsgs/dismsgs/quarmsgs | "T"接頭辞 |
| 2 | 接続行(C) | 接続統計 | stats.stat_cf/stat_ct/stat_cr | "C"接頭辞 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 統計データ存在 | stat_nf/stat_nt/stat_nq/stat_nr/stat_nd のいずれかが非ゼロ | 自動（ゼロのメーラーは非表示） |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | メーラー番号 | 昇順（0からMAXMAILERS-1） |

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| sendmail統計ファイル | 統計データ読み取り | StatusFileオプションで指定 |
| sendmail設定ファイル | メーラー名テーブル構築 | sendmail.cf のM行 |

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

#### sendmail統計ファイル（struct statistics）

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| stat_itime | 統計開始時刻 | read() | time_t型 |
| stat_nf[i] | 送信メッセージ数 | read() | メーラーiの値 |
| stat_bf[i] | 送信バイト数 | read() | キロバイト単位 |
| stat_nt[i] | 受信メッセージ数 | read() | メーラーiの値 |
| stat_bt[i] | 受信バイト数 | read() | キロバイト単位 |
| stat_nr[i] | 拒否メッセージ数 | read() | メーラーiの値 |
| stat_nd[i] | 破棄メッセージ数 | read() | メーラーiの値 |
| stat_nq[i] | 検疫メッセージ数 | read() | メーラーiの値 |
| stat_cf | 接続from数 | read() | |
| stat_ct | 接続to数 | read() | |
| stat_cr | 接続reject数 | read() | |
| stat_magic | マジックナンバー | read() | STAT_MAGIC検証 |
| stat_version | バージョン | read() | STAT_VERSION検証 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 合計送信メッセージ数 | SUM(stat_nf[0..MAXMAILERS-1]) | N/A | frmsgs変数 |
| 合計送信バイト数 | SUM(stat_bf[0..MAXMAILERS-1]) | N/A | frbytes変数 |
| 合計受信メッセージ数 | SUM(stat_nt[0..MAXMAILERS-1]) | N/A | tomsgs変数 |
| 合計受信バイト数 | SUM(stat_bt[0..MAXMAILERS-1]) | N/A | tobytes変数 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[mailstatsコマンド実行] --> B[オプション解析]
    B --> C[sendmail.cf読み込み]
    C --> D[メーラー名テーブル構築]
    D --> E[StatusFileパス決定]
    E --> F[統計ファイル読み込み]
    F --> G{マジックナンバー・バージョン検証}
    G -->|OK| H[メーラー別データ出力ループ]
    G -->|NG| Z[エラー終了]
    H --> I[合計行出力]
    I --> J[接続統計出力]
    J --> K{-pオプション?}
    K -->|Yes| L[統計ファイルtruncate]
    K -->|No| M[終了]
    L --> M
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 設定ファイルオープン失敗 | sendmail.cfが読めない | "mailstats: {file}" | exit(EX_NOINPUT) |
| 統計ファイルなし | StatusFileが見つからない | "no statistics file located" | exit(EX_OSFILE) |
| 統計ファイル読み取り失敗 | read()失敗 | "mailstats: {file}" | exit(EX_NOINPUT) |
| マジックナンバー不一致 | stat_magic != STAT_MAGIC | "incorrect magic number" | exit(EX_OSERR) |
| バージョン不一致 | stat_version != STAT_VERSION | "version incompatible" | exit(EX_OSERR) |
| メーラー数超過 | mno >= MAXMAILERS | "Too many mailers defined" | exit(EX_SOFTWARE) |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | MAXMAILERSエントリ（固定サイズ） |
| 目標出力時間 | 即時（統計ファイルは固定サイズ） |
| 同時出力数上限 | 制限なし |

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

- sendmail設定ファイルの読み取りが必要
- 統計ファイルの読み取り権限が必要
- -p オプションで統計ファイルを切り詰め（O_TRUNC）可能なためroot権限推奨

## 備考

- ソースコードはsendmailのcontribディレクトリに配置（contrib/sendmail/mailstats/）
- sendmailのlibsm/libsmutilに依存
- 事前定義メーラー: "prog"(0), "*file*"(1), "*include*"(2)（行133-135）
- -P オプションでプログラム可読形式出力（バイト数の"K"サフィックスなし）
- -p オプションはプログラム可読形式出力後に統計ファイルを切り詰め
- -o オプションでメーラー名表示を抑制
- -c/-C オプションでsubmit.cf/カスタム設定ファイルを指定可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | mailstats.h | `contrib/sendmail/include/sendmail/mailstats.h` | struct statistics の定義、STAT_MAGIC、STAT_VERSION |
| 1-2 | mailstats.c | `contrib/sendmail/mailstats/mailstats.c` | MNAMELEN定義（行43）、mtable配列（行64） |

**読解のコツ**: sendmail固有のI/Oライブラリ（sm_io_*関数）を使用しているが、本質的にはfprintf相当である。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | mailstats.c | `contrib/sendmail/mailstats/mailstats.c` | main()（行45-372）：全処理が単一関数に実装 |

**主要処理フロー**:
1. **行73-115**: オプション解析（-c, -C, -f, -o, -p, -P）
2. **行122-211**: sendmail.cf読み込みとメーラー名テーブル構築
3. **行223-274**: 統計ファイル読み込みと検証
4. **行277-329**: メーラー別統計出力ループ
5. **行330-368**: 合計行・接続統計出力

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

```
main() [mailstats.c:45]
    |
    +-- getcfname() [設定ファイルパス取得]
    +-- getopt() [オプション解析]
    |
    +-- sm_io_open() [sendmail.cf読み込み]
    +-- sm_io_fgets() [設定行読取り]
    |   +-- Mで始まる行 → メーラー名抽出
    |   +-- OでStatusFileオプション → 統計ファイルパス取得
    |
    +-- open()/read() [統計ファイル読み込み]
    |
    +-- sm_io_fprintf() [統計データ出力]
    |   +-- メーラー別ループ (i=0..MAXMAILERS-1)
    |   +-- 合計行 (T)
    |   +-- 接続行 (C)
    |
    +-- open(O_RDWR|O_TRUNC) [-p時: 統計リセット]
```

### データフロー図

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

sendmail.cf ─────────> 設定ファイルパース ──────> メーラー名テーブル
  (M行/O行)             メーラー名抽出
                        StatusFileパス取得

統計ファイル ────────> read() ─────────────────> stdout
  (struct statistics)   マジック/バージョン検証     (テキスト)
                        メーラー別集計出力
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| mailstats.c | `contrib/sendmail/mailstats/mailstats.c` | ソース | メインプログラム |
| mailstats.h | `contrib/sendmail/include/sendmail/mailstats.h` | ヘッダ | statistics構造体定義 |
| pathnames.h | `contrib/sendmail/include/sendmail/pathnames.h` | ヘッダ | デフォルトパス定義 |
| Makefile | `usr.sbin/mailstats/Makefile` | ビルド | ビルド設定（libsm/libsmutil依存） |
