おい、最近配属された若手が「可変長レコードのファイル入出力で、なぜかS0C4やデータ落ちが起きるんです」って青い顔をして泣きついてきたんだ。話を聞くと、ネットで見つけた表層的な解説を鵜呑みにして、RDW(Record Descriptor Word)の構造をナメてかかっていたらしい。
メインフレームの現場において、PL/Iのファイル入出力、特にVSAMのESDSやRRDS、あるいはBPAMやQSAMのV/VB形式を扱うときの `ENVIRONMENT` 属性、そして `RECSIZE` の指定は、システム全体のパフォーマンスとデータ整合性を左右する極めて重要な心臓部だ。
今回は、PL/Iの変態的なまでに柔軟な(そして一歩間違えると牙をむく)識別子と予約語の仕様、そして可変長レコードにおけるRDWの裏側の仕組みを、ベテランの視点から徹底的に叩き込んでやろう。
—
1. PL/Iの「予約語を持たない」言語仕様と実務的リスク
まず前提として、C言語やJavaのように「`if` や `while` などのキーワードを変数名に使えない」という厳格な予約語の概念が、実はPL/Iの基本設計には存在しない。コンパイラは文脈(Context)からそれがキーワードなのかユーザー定義の識別子なのかを判断する。
例えば、極端な話、`IF` という名前の変数を作ることも理論上は可能だ。しかし、これを現場のバッチプログラムでやったらどうなるか? コードレビューで大目玉を食らうどころか、コンパイルエラーか、あるいは人知を超えた文法解釈のバグによって深夜の障害を引き起こす元になる。
「予約語がない=何でも自由に命名していい」という誤解は今すぐ捨ててくれ。可読性と保守性の観点から、標準的なキーワードは変数名として絶対に使わない、これが現場の鉄則だ。
—
2. 可変長レコード(V/VB形式)とRDWの残酷な真実
さて本題だ。Q/ISAMやQSAMで可変長(V形式:Variable、VB形式:Variable Blocked)のデータセットを扱うとき、PL/Iのプログラム側ではどのようにレコードが見えているのだろうか。
物理的なレコードの先頭には、必ず RDW(Record Descriptor Word) という4バイトのプレフィックスが付与されている。
- 前半の2バイト: レコード全体のバイト長(LL:Length、バイナリ値)
- 後半の2バイト: 予約領域(通常は `X’0000’`)
初心者エンジニアがやりがちな致命的なミスが、`ENVIRONMENT(RECSIZE(…))` の指定だ。
VSAMやQSAMで可変長ファイルを定義する際、`RECSIZE` には「データ部(LRECL)の最大値」を指定するのか、それとも「RDW(4バイト)を含んだサイズ」を指定するのか、混乱する現場を何度も見てきた。
結論から言おう。
QSAMのV/VB形式をPL/Iで処理する場合、`RECSIZE`(またはDCBのLRECL)にはRDWを含んだサイズを指定するのが鉄則だ。
例えば、アプリケーション側で扱いたい最大データ長が100バイトだとすると、ファイル上の物理レコード長はRDWの4バイトを含めて `104` になる。ここを誤ると、PL/Iランタイムの内部ポインタ操作が狂い、データがズレるか、最悪の場合はストレージ保護例外(S0C4)で夜間バッチが仲良くクラッシュすることになる。
PL/Iランタイムの内部ポインタ操作の裏側
PL/Iで `READ FILE(…) INTO(…)` を実行したとき、コンパイラが生成するランタイムコードは、裏側で以下のような血のにじむようなポインタ演算を行っている。
1. OSのアクセスメソッドからバッファに読み込まれた生データを受け取る。
2. 先頭4バイトのRDW(LL部)を読み取り、残りのデータ長を動的に算出する。
3. `INTO` で指定された構造体や変数の領域へ、メモリをコピー(あるいはベースポインタをシフト)する。
このとき、もし `ENVIRONMENT` 属性の指定や構造体の定義(特に不連続なオーバーレイや不正なベースポインタ)がズレていると、ランタイムが不正なメモリ領域を指してしまい、一瞬でアボートする。
—
3. 実践コード:環境属性と安全な可変長レコード処理
百聞は一見に如かず。実際に現場で使える、堅牢な可変長レコード入出力のサンプルコードを示そう。大文字ベースで記述し、BUILTIN関数(内蔵関数)を適切に活用した模範的なコードだ。
1
—————————————————————-
- プログラム名: VREC01PR
- 概要 : 可変長ファイル(VB)の安全な読込とレコード長検証
—————————————————————-
VREC01PR: PROC OPTIONS(MAIN);
— ファイル定義 (QSAM VB形式) —
- LRECL=104 (データ100バイト + RDW4バイト) を想定 変数の予約語代わりにご注意
DCL IN_FILE FILE RECORD INPUT
ENVIRONMENT(
CB(80)
RECSIZE(104)
VB
);
— 終了判定フラグ —
DCL EOF_FLG BIT(1) INIT(‘0’B);
— 入力レコード用構造体(先頭にRDW分の領域を必ず確保する) —
DCL 1 IN_RECORD,
5 RDW_LEN FIXED BIN(15), / レコード長 (LL) /
5 RDW_RES FIXED BIN(15), / 予約領域 (X’0000’) /
5 DATA_BODY CHAR(100); / 実データ部 /
— 異常系制御のためのONユニット —
ON ENDFILE(IN_FILE)
EOF_FLG = ‘1’B;
ON UNDEFINEDFILE(IN_FILE) BEGIN;
PUT SKIP LIST(‘ ERROR: INPUT FILE OPEN FAILED ‘);
SIGNAL ERROR;
END;
— ファイルオープン —
OPEN FILE(IN_FILE);
PUT SKIP LIST(‘=== V-FILE READ START ===’);
— メインループ —
READ FILE(IN_FILE) INTO(IN_RECORD);
DO WHILE(^EOF_FLG);
——————————————————–
- [重要] RDW長(RDW_LEN)のバリデーション
- 実データ長が想定範囲内か、組み込み関数を活用して検証
——————————————————–
IF RDW_LEN < 4 | RDW_LEN > 104 THEN DO;
PUT SKIP EDIT(‘INVALID RDW LENGTH DETECTED: ‘, RDW_LEN)
(A, F(5));
- 必要に応じてここでエラー処理やABENDへ飛ばす
END;
ELSE DO;
— 正常なレコードの処理(例として先頭データを表示) —
- ※実際の業務ではここでビジネスロジックを展開
/ SUBSTRBUILTIN関数等でデータを安全に切り出す /
/ ここでは簡易的にDATA_BODYを出力 /
END;
— 次のレコード読み込み —
READ FILE(IN_FILE) INTO(IN_RECORD);
END;
— ファイルクローズ —
CLOSE FILE(IN_FILE);
PUT SKIP LIST(‘=== V-FILE READ NORMAL END ===’);
END VREC01PR;
—
4. 現場のベテランからのアドバイスとデバッグのコツ
このコードを見て、「なぜわざわざ構造体の中に `RDW_LEN` や `RDW_RES` なんて定義しているんだ? `INTO` するならデータ部だけでいいじゃないか」と思ったそこの君。そこが大きな落とし穴だ。
PL/Iで `INTO` を使う場合、定義した構造体のサイズと、ファイル定義(`ENVIRONMENT(RECSIZE)`)のサイズ、そして実際に物理レコードが持っているバイト長が完璧に一致していなければ、ランタイムエラーやメモリ破壊の温床になる。
特に `BASED` ストレージやポインタ (`POINTER`) を使ったストリーミング処理を行う場合、RDWを意識したオフセット計算を怠ると、一発でプロダクション環境を止める大障害に繋がりかねない。
もし君が担当しているバッチで原因不明の `IBM0201S`(ような入出力エラー)や `S0C4` に遭遇したら、以下のポイントを真っ先に疑ってくれ。
1. JCLのDCBパラメータ(LRECL, BLKSIZE)と、PL/I側の `ENVIRONMENT(RECSIZE(…))` が完全に一致しているか?
2. 可変長レコードを処理する際、構造体のオフセットがRDWの4バイト分を考慮して設計されているか?
3. コンパイラの最適化オプションによってポインタの参照先がレジスタにキャッシュされ、意図しない上書きが発生していないか?(必要に応じた `REORDER` / `NOREORDER` の確認)
基幹システムの命を守るのは、いつだってこうした泥臭い仕様の理解と、細部へのこだわりだ。マニュアルの文字面をなぞるだけではなく、「メモリの裏側でランタイムが何をしているか」を脳内でビジュアライズできるようになれば、君も立派なメインフレーム・アーキテクトだ。次のバッチ改修も、その調子で確実に乗り切ってくれよ。
