【PL/Iアーキテクチャ解体新書】SYSPRINTの改行魔術:LINESIZEとPAGESIZEが物理レコードに刻む爪痕
メインフレームの現場で長年システムを支えてきた者にとって、JCLの`SYSOUT`やPL/Iの`STREAM`出力は、ある種の「枯れた技術」として見過ごされがちだ。しかし、JavaやC#といったモダン言語出身のエンジニアがレガシー移行(マイグレーション)の現場に参画した際、この「ストリーム出力と物理レコードの境界」という古くて深い仕様の壁にぶつかり、謎の切り捨てや改行抜け、果てはデータ破損に頭を抱えるシーンを幾度となく目撃してきた。
今回は、PL/Iにおける`STREAM`ファイル出力、とりわけ`LINESIZE`と`PAGESIZE`がコンパイラやOS(z/OSのBSAM/QSAM)のバッファリング、そして物理レコード(ブロック)の分割にどう影響を与えるのか。その内部挙動を、コンパイラの最適化、動的制御、さらにはマイグレーション時のエッジケースまで含めて徹底的に解き明かしていく。
—
1. STREAM出力における LINESIZE と PAGESIZE の本質
C言語の `printf` や Java の `System.out.println` の感覚で PL/I の `PUT FILE(SYSPRINT) LIST(…)` を扱うと、必ず痛い目を見る。PL/I のストリーム入出力は単なるバイト列の流し込みではなく、「論理的な印刷ページ(Page)」および「行(Line)」の概念をランタイム環境が厳密に管理する高度なレイヤーだからだ。
PAGESIZE と LINESIZE のデフォルトの呪縛
何も指定せずに `SYSPRINT` をオープンすると、Enterprise PL/I コンパイラおよびランタイム(LE: Language Environment)は、デフォルトで以下のような暗黙の制約を課す。
- LINESIZE: デフォルトは通常 120 バイト(環境やDCBに依存するが、一般的に端末やプリンタのレガシーな規格を引き継ぐ)
- PAGESIZE: デフォルトは 60 行
ここで恐ろしいのは、`LINESIZE` で指定した長さを超えた文字を 1つの `PUT` ステートメントで出力しようとした際のエラー、あるいは暗黙の折り返し(Wrap-around)である。
1
DCL LONG_LINE CHAR(200) INIT(‘A…(200文字の文字列)…Z’);
/ LINESIZE(80) の状態でこれを流すとどうなるか? /
PUT FILE(SYSPRINT) DATA(LONG_LINE);
コンパイラは、`LINESIZE` で定められた限界を超えた瞬間、内部で強制的にキャリッジコントロール(改行)を挿入するか、あるいは環境によっては `IBM0216I` などのランタイムメッセージを出力して処理を異常終了(ABEND: S0U0 / U4038など)させる。
—
2. 物理レコード(LRECL/BLKSIZE)と改行コードの自動付与ロジック
では、この `LINESIZE` は、JCL側で定義されたDCBパラメータ(`LRECL` や `BLKSIZE`)とどのように相互作用するのだろうか。ここがメインフレームアーキテクチャの最も痺れるところだ。
ASA制御文字(VBA/FBA)の罠
通常、`SYSPRINT` はレコードフォーマットとして `FBA`(固定長ブロック・ASA制御文字付き)または `VBA`(可変長ブロック・ASA制御文字付き)が使用される。
PL/Iが `PUT` を実行する際、ランタイムは以下のような物理的変換を行う。
1. 論理レコードの構築: `LINESIZE` で指定されたバイト数ごとにデータを区切る。
2. ASA制御文字の付与: レコードの先頭1バイトに、プリンタ制御用の文字(` `(スペース)= 改行して1行出力、`0` = 2行送る、`1` = 改ページ など)を自動的に付与する。
3. OS(QSAM)への引き渡し: 結果として、JCLの `LRECL` は `LINESIZE + 1(ASA制御文字分)` 以上のサイズが強制される。
もし、JCL側の `LRECL` が `LINESIZE` よりも小さく定義されていた場合、ランタイムは `IGZ016S` や `IBM0223I` のような致命的なランタイムエラーを吐いてアベンドする。
—
3. 実践:動的制御とポインタを用いたストリームバッファのハック
基幹システムのバッチ処理において、帳票のレイアウト変更や、可変長なダンプ出力を動的に制御したい場合、静的な `OPEN` ではなく、ベース変数(Based変数)とポインタを駆使した高度なバッファ制御が要求される。
以下に、動的に `LINESIZE` を再定義しつつ、メモリ上で安全にストリームデータを構築する実践的なコード例を示す。
1
———————————————————————-
- プログラム名: STRMCTL0
- 概要: 動的LINESIZE制御とポインタベースのストリーム出力処理
———————————————————————-
STRMCTL: PROC OPTIONS(MAIN);
— 変数宣言 —
DCL OUT_FILE FILE STREAM OUTPUT;
DCL DYNAMIC_LRECL FIXED BIN(31) INIT(132); / 動的な行長 /
DCL RET_CODE FIXED BIN(31) INIT(0);
— ベース変数(動的メモリ割り当て用) —
DCL 1 PRINT_RECORD BASED(PTR_REC),
2 ASA_CTL CHAR(1), / ASA制御文字 /
2 LINE_BODY CHAR(132); / 印字データ本体 /
DCL PTR_REC PTR;
DCL WORK_STORAGE CHAR(256) BASED(PTR_WORK);
DCL PTR_WORK PTR;
— ヒープ領域からのメモリ動的獲得 —
ALLOCATE PRINT_RECORD SET(PTR_REC);
ASA_CTL = ‘ ‘; / スペース: 通常改行 /
LINE_BODY = ‘ ‘;
— ファイルオープン(LINESIZEを動的に指定) —
- 注: 実際にはOPEN TITLE句や環境変数、またはDCB統合で制御するが、
- PL/IのENVIRONMENT(LINESIZE(n))オプションの動的模倣を行う。
OPEN FILE(OUT_FILE)
OUTPUT
TITLE(‘SYSPRINT’)
LINESIZE(132)
PAGESIZE(66);
— ストリーム出力の実行 —
ON ERROR
BEGIN;
PUT SKIP LIST(‘ 致命的なストリームエラーが発生しました ‘);
GOTO ERROR_HND;
END;
- データを構築して出力
LINE_BODY = ‘【月次売上実績レポート】 処理日付: 202X年XX月XX日’;
PUT FILE(OUT_FILE) EDIT(PRINT_RECORD)(A);
LINE_BODY = ‘—————————————————————————————————-‘;
PUT FILE(OUT_FILE) EDIT(PRINT_RECORD)(A);
— クローズ処理 —
CLOSE FILE(OUT_FILE);
GOTO NORMAL_END;
ERROR_HND:
RET_CODE = 8;
/ 異常終了時のハンドリング /
NORMAL_END:
— メモリ解放 —
FREE PRINT_RECORD;
RETURN;
END STRMCTL;
このコードでは、`BASED` 変数を用いることで、メモリ上のレイアウトを完全に手元でコントロールしつつ、PL/Iのストリーム出力エンジンにデータを渡している。レガシーシステムのチューニングにおいて、不要なI/Oオーバヘッドを削減するためにバッファリングを自前でシミュレートする際によく使われるテクニックだ。
—
4. マイグレーション時(Java/C#等への移行)のエッジケースと罠
さて、ここからが現代のシステムアーキテクトにとって最も重要なトピックだ。このPL/Iの `LINESIZE` / `PAGESIZE` および ASA制御文字の仕様を、そのまま Java(Spring Batch など)や C# (.NET Core) にリプレイスする際、どのような地雷が埋まっているか。
1. 改行コード(CR/LF vs ASA制御文字)の消失
オープン系言語のファイル出力は、基本的に行末に `CR+LF`(Windows)または `LF`(Linux)を付与する。しかし、メインフレームの `SYSPRINT`(FBA/VBA)は、行末に改行コードを持たず、レコードの先頭にある「ASA制御文字」によって改行を表現している。
これをそのままLinux環境に持っていき、単純なバイトストリームとしてファイル化すると、「全行が1行につながった文字化けファイル」が誕生する。マイグレーション時には、ASA制御文字をパースし、適切な `\n` やフォームフィード(`\f`)に置換するミドルウェア層の変換ロジックが絶対に必要となる。
2. パックデシマルとストリーム出力の暗黙的変換バグ
PL/Iでありがちなのが、`FIXED DECIMAL`(パックデシマル)変数を `EDIT` 編集せずに `LIST` でそのままストリーム出力しようとするケースだ。コンパイラはこれを勝手に文字列表現に変換して出力するが、内部符号(ゾーン部・サイン部)がマイナスの値(例: `{` や `}`、あるいはゾーン反転したバイナリ)が含まれている場合、`LINESIZE` のカウントが狂ったり、出力先のエディタが文字化けを起こしたりする。
Javaへの移行時、このパックデシマルの符号反転を見落とし、マイナス金額が正の値として出力される致命的なバグ(データ不整合)が多発する。移行設計では、PL/I側の `PIC` 句の定義を完全に解析し、Java側では `BigDecimal` と厳密なフォーマッタ(DecimalFormat)で再実装しなければならない。
—
5. アーキテクトとしての総括
PL/Iの `STREAM` ファイル、そして `LINESIZE` と `PAGESIZE` の制御は、単なる「文字の切り捨てを防ぐ設定」ではない。それは、ハードウェアの制約、プリンタの物理的限界、そしてOSのアクセスコントロールが三位一体となった、メインフレーム時代の「美しき制約芸術」である。
レガシーマイグレーションを成功させる秘訣は、古いコードを単にモダンな言語に機械的(トランスレータ頼み)に翻訳することではない。そのコードが背負っている「メインフレームのハードウェア的背景とランタイムの挙動」を完全に理解し、新環境でどうエミュレートするか、あるいはどうモダンな仕様に昇華させるかを設計することに他ならない。
基幹システムの信頼性を1ミリも落とさないために。このプラットフォームの深層を知り尽くしたアーキテクトとしての誇りを持って、次の移行プロジェクトに挑んでほしい。
