IBM iの新規スプール・ファイルの保存
QSRSAVO API を使って保存対象とするスプール・ファイルを選択する
遠くさかのぼることSystem/38の時代に、スプール・ファイルを保存してくれる機能がO/Sにあればなあと思ったものです。そのような機能の要件を作成し、COMMONカンファレンスの特別セッションで必要性を主張したことさえありました。聴衆の反応を見てみると、私のことを変人だと思った人がほとんどのようでした。でも私はそれが理解できませんでした。というのも今まで私が勤務した会社ではいずれも、ユーザーはスプール・ファイル(特に月末や年末のレポート)がデータベースと同じくらい重要なものであると考えていたからです。当時IBMに関していえば、スプール・ファイルのバックアップを取る最良の手段は、それを印刷しておくことでした。(もちろん、スプール・ファイルを物理的なファイルやユーザー空間にコピーしてそこからさらにそのファイルを保存することで、O/Sの管理外でバックアップを取る手段は他にもありますが、そうした手段はいつもその場しのぎの解決に終わることが多く、必要なソフトウェアが備わっていない他のシステムにバックアップしたファイルを回復しようとするときに問題となります。)
ロチェスター事業所がついにSystem i 5.4でスプール・ファイルを保存する機能のサポートを提供した時、私はバックアップ・プログラムが毎日そして毎週スプール・ファイルを保存できるように即座に変更を始めました。スプール・ファイル・データ(SPLFDTA)パラメータがSAVCHGxxxコマンドのいずれにも存在していないとわかった時の私の苛立ちを想像してください。ありがたいことにIBMは、このタスクをオブジェクト・リスト保存(QSRSAVO: Save Object List) APIを使用して遂行する方法を提供してくれました。このAPIは、基本的にはライブラリ保存(SAVLIB: Save Library)コマンドとオブジェクト保存(SAVOBJ: Save Object)コマンドと似ていますが、保存の対象となるスプール・ファイルを選択する機能が加わっています。この機能を使用すると、特定の日時以降に作成されたスプール・ファイルだけを保存することができます。(ILE RPGでこれができるなら、IBMはなぜSAVCHGxxxコマンドで同じことができるようにしないのだろう、という疑問がわいても不思議ではないでしょう。)
新規スプール・ファイル保存(Save New Spooled Files)プログラムはSystem iNetworkコードのウェブ・サイトからダウンロードできますし、peterlevy.comのページを開いて[Downloads]をクリックし、次に[SaveNewSploolFiles.zip]をクリックすればダウンロードできます。ファイルをダウンロードしたら、解凍してReadMe.html をウェブ・ブラウザで開いてください。ソースコードをアップロードしMAKE CLプログラムを使用してオブジェクトを作成する全手順が、このドキュメントに記載されています。
QSRSAVO API
QSRSAVO APIには、条件を完全に満たしたユーザー空間名とエラー・コード・データ構造の2つのパラメータだけがあります。ILE RPGのプロトタイプを図―1に示します。このAPIを使用するプログラムはすべて、APIを呼び出す前にユーザー空間中に保存するためのパラメータを保存しておかなければなりません。(この点がSAVxxxコマンド・パラメータと若干異なる部分ですが、これについてはあとでもう少し説明します。) 保存中にエラーが発生した場合、エラー・コード・パラメータにメッセージIDとメッセージ・データが入ります。
最初にしなければならないのはユーザー空間を作成することです。ユーザー空間の作成については以前に解説しましたし(「ユーザー空間の自動拡張」、記事ID 19590)、必要なサービス・プログラムをダウンロード可能なコードとして用意してあります。ユーザー空間を作成し、そのサイズを自動的に拡張できるように変更し、呼び出し側プログラムに戻されたポインタを取り出すには3つのシステムAPIが必要になるといっておけば十分でしょう。このポインタはユーザー空間内のデータ空間を直接指しており、保存用パラメータの構築に使用されます。
次に、上記のパラメータをユーザー空間に追加します。このパラメータは、様々なSAVxxxコマンドのパラメータに対してほとんどの場合正確に、場合によっては大まかに対応している可変長のレコードの集合で構成されています。またこのAPIはパラメータ名の代わりに「キー番号」が必要となります。たとえば、LIBパラメータはキー番号2、DEVパラメータはキー番号3、OBJパラメータとOBJTYPEパラメータは結合されてキー番号1といった具合です。
すべてのパラメータに対して使用した2つのデータ構造を図―2に示します。ユーザー空間の最初の4バイトはレコードの総数(SavObj.RcdNbr)を含んだバイナリ整数となっています。その次に最初のレコードが続きます。この最初のレコードとそれ以後のすべてのレコードは同じデータ構造(SavObjParms)をしており、3つの整数とそれに続くデータからなっています。最初の整数にはレコード全体の長さ(RcdLen)、2番目の整数にはパラメータのキー番号(KeyNbr)、3番目の整数にはデータの長さ(DataLen)が入っています。SavObjデータ構造のベースとなるポインタであるSplfUsrSpcPtrにもユーザー空間へのポインタが保存されており、これが変更されることは決してないという点に注意してください。SavObjParmsデータ構造へのポインタは新しいレコードが来るたびに増やされます。(1つの巨大なデータ構造を作成してすべての位置をハードコードすれば、ポインタを使用してそれを理解する必要をなくすこともできますが、あとでプログラムを修正する必要が生じた時にきわめて柔軟性に欠けることになります。)
これでオブジェクトとオブジェクト・タイプのリストからなる最初のパラメータを構築しました。使用するRPGコードを図―3に示します。このコードは以下のステップを実行します。
- 整数SavObj.Rcdnbrを最初のレコード用に1に初期化します。
- SavObjParmsデータ構造へのポインタ(SavObjParmsPtr)が整数SavObj.Rcdnbr分だけ進んだ位置になるように計算します。
- RcdLenフィールド、KeyNbrフィールド、DataLenフィールドをそれぞれ、レコード長、OBJ/OBJTYPEパラメータ(1)のキー番号、データ長に初期化します。
- SavObjParmListデータ構造へのポインタ(SavObjParmPtr)が整数DataLen分だけ進んだ位置になるように計算します。
- 今回の場合、データがリストなので、データの最初の項目はリスト中にいくつの要素があるかを示す4バイトの整数となっています。ここではリスト中には要素が1つしかありませんから、この最初の項目は1 (ElemNbr)に初期化されます。
- 図―4に示すObjObjTypeParmデータ構造へのポインタ(SavObjElemPtr)が整数ElemNbr分だけ進んだ位置になるように計算します。
- 最後に、ObjObjTypeParmデータ構造の2つの要素を*ALLオブジェクトと*ALLオブジェクト・タイプに初期化します。
このプログラムはスプール・ファイルしか保存しませんが、*ALL オブジェクトと*ALL オブジェクト・タイプを指定している最初のパラメータを指定しなければなりません。
ライブラリのリスト用の2番目のパラメータを構築するコードは図―5にあり、以下の点を除くと図―3のコードに似ています。
- 整数SavObj.Rcdnbrを2番目のパラメータ・レコード用に1だけ増やします。
- データ構造SavObjParms用のポインタ(SavObjParmsPtr)の計算は前のパラメータ・レコードのRcdLen値を使用して、今進んだ分の新しい位置にセットします。
- KeyNbrをLIBパラメータ(2)のキー番号で初期化します。
- データ部分には保存対象のライブラリのリストが含まれています。今回はスプール・ファイルだけを保存しますので、特別値*SPLFを置き換えます。LIBパラメータ・データ用のデータ構造を図―6に示します。
ユーザー空間にパラメータを追加する上記の方法は、パラメータを追加し終えるまで続きます。ほとんどすべての有効なパラメータを新しいスプール・ファイルをバックアップするために使用することができ、QSRSAVO APIドキュメントの有効なキー・チャートにすべてが記載されています。他のパラメータに比べて意味のあるパラメータもあります。たとえば、save-while-activeを使用してスプール・ファイルをバックアップ保存することができますので、SAVACTとそれに関連するパラメータは有用です。一方、スプール・ファイルがバックアップ保存されると、保存されたスプール・ファイル用の記憶域は二度と開放されませんので、STGパラメータを含めるのはコードの無駄遣いになります。
RcdLenフィールドとDataLenフィールド
SavObjParmsデータ構造はなぜデータのサイズに加えてレコード全体のサイズを必要とするのか、と疑問に思うかもしれません。結局、このAPIはRcdLenフィールドとKeyNbrフィールドのサイズを知っていますから、この2つのフィールドをレコード長から差し引いてデータ長を計算したりその逆を計算したりというのが容易に行えるはずです。
上記のような疑問を持たれても不思議ではありませんが、これは境界問題に関連している場合がほとんどなのです。4バイトの境界を持つバイナリ整数は、すべての整数が4バイトの境界上にあればより効率的に処理することができます。たとえば、14バイトのデータ(単一の要素であるデバイスやライブラリ名のリスト、など)があり、そこに3つの整数が加わるとレコード長は26バイトになります。こうすると次のレコードは4バイトの境界のちょうど中央から始まることになってしまいます(図―7)。データをダンプしてアドレスx'0021'を見てみると、最後の整数であるx'1A'が最初のカラムの中央にあるのがお分かりいただけると思います。これはこの整数がアドレスx'001E'から始まっているからです。
4バイトの境界にデータを揃えて効率よく処理できるようにするには、レコード長を28バイトに増やせばよいのですが、データ長はもちろん14バイトのままです(図―8)。こうすると追加した2バイトが使用されないままになってしまいますが、たいしたことではありません。これでダンプを見てみると、同じ整数がアドレスx'0020'で始まってアドレスx'0023'で終わっており、世の中すべてうまくいくことになります。
さらに効率的にするために、SetIntBoundary()という内部関数も作成しました(図―9)。レコード長が4で等分に割り切れない場合は、この関数が4で等分に割り切れる長さに増やして返してくれます。レコード長が4で等分に割り切れる場合は、元の長さを返してくれます。図―3のレコード長の計算にこの新しい関数を使用するよう変更しました(図―10中のA)。
SPLFDTA (Key #35)パラメータ
ユーザー空間に追加されるパラメータのほとんどは、平凡な単一パラメータか簡単に理解できるリスト・パラメータです。SPLFDTA (Key #35)パラメータは少し複雑です。というのはスプール・ファイルを選択する方法が複数あるためです。このパラメータを処理するのに使用するデータ構造を図―11に、プログラム・コードを図―12にそれぞれ示します。データの最初の部分は、SavObjParmsデータ構造中の他のパラメータ(つまり RcdLen、KeyNbr、DataLen フィールド)と同じです。図―12 のAで、DataLenはSplfDtaParm、SplfDtaSelect、SplfDtaAttrの各データ構造のサイズの合計値が入っています。なぜかというと、新規のスプール・ファイルを保存するのに必要なのはこれだけだからです。このパラメータをこれ以外に使用するのは長くて複雑な処理になりますので、保存対象について必要に応じてきめ細かく理解しなければならないこともありますが、今回のプログラムではそれほど細かく理解する必要はありません。
SavObjParmsデータ構造が初期化できたら、このプログラムは次にSplfDtaParmデータ構造に値を代入できるように今進んだ分だけポインタを計算します(図―12中のB)。最初に初期化するフィールドはSplfDataで、*ZERO (スプール・ファイルを保存しない)、1 (すべての保存されている出力キューについてそこに含まれているスプール・ファイルを保存する)、2 (選択されたスプール・ファイルだけを保存し、この場合選択基準を追加する必要がある)の3つの値のいずれかとなります。今回のプログラム例ではオプション2を使用しています。
次に初期化するフィールドはSplfHdrLenフィールドで、SplfDtaParmデータ構造の長さを表すフィールドです。このフィールドは前述のSplfDataフィールドに指定されている値によって2つの値を取り得ます。SplfDataフィールドに*ZEROか1が指定されている場合は、SplfHdrLenフィールドの値は8となり、これはデータ構造中のSplfDataフィールドとSplfHdrLenフィールドだけをカバーする長さです。(このフィールドを設計した開発者がなぜSplfHdrLenフィールドをこのデータ構造中の2番目に置いたのかよくわかりません。SplfHdrLenフィールドを1番目に置いた方が理屈が通るのですが、これについてはまた別の機会に検討しましょう。) もう一つの取り得る値は12で、これはデータ構造中の最初の2つのフィールドだけでなく最後のフィールドもカバーします。図―12に示すプログラムのBでは、SplfData構造の全サイズをカバーする12を指定しています。
最後のフィールドは、SplfDataフィールドの値が2に設定された時に特に必要となるフィールドです。SplfOffsetフィールドはスプール・ファイルの選択基準に対するオフセットを示します。IBMの専門用語で「オフセット」とは、ユーザー空間内の現在のセクションの始まりからではなく、ユーザー空間の始まりからの開始位置のことを言い、その距離を「変位」と言います。(変位がどのように使用されているかの例としては、ユーザー空間内のいろいろなパラメータ・レコードの長さがあります。) ユーザー空間の真っただ中にいるときにオフセットを計算するもっとも簡単な方法は、変位を使用して新しいポインタを新しい位置に設定し、新しいポインタ値からベースのユーザー空間のポインタを引けばよいのです。
スプール・ファイルの選択基準はデータ構造のリストであり、特定のスプール・ファイルの集合をバックアップの対象として選択したり、バックアップの対象から除外したりすることができます。今回の例ではSplfDtaSelect構造は図―11中のBにあり、この構造を初期化しているコードは図―12中のCにあります。この構造を使用すると、個々のスプール・ファイルあるいはスプール・ファイル全体の集合を選択あるいは除外したりすることができます。(また、この構造を使用して各スプール・ファイルの集合を保存した後にその有効期限を変更することもできますが、これについては本稿では触れません。)
このデータ構造の最初のフィールドはLengthという名前で、SplfDtaSelect構造の長さを表します。このフィールドが取り得る値は20 (新しい属性を設定するためのオフセットを除外する)、または24 (新しい属性を設定するためのオフセットを含める)のいずれかです。ここでは、このフィールドの値を24に設定しても、結局はオフセットを*ZEROに設定することになります。
2番目のフィールドはOffsetという名前で、その名前からわかる通り次のスプール・ファイル選択基準構造までのオフセットを表します。しかしここでは必要な選択基準データ構造は1つだけなので、この値を*ZEROに設定して、APIに対して現在の選択基準がリスト中の最後のつまり唯一の選択基準であると伝えます。
3番目のフィールドはIncludeという数値のインジケータで、スプール・ファイルを含めるのか除外するのかをAPIに対して伝えます。*ZEROを設定すると除外、1を設定すると含めることになります。 もちろんここでは1を設定します。
4番目のフィールドはFormatというフィールドで、スプール・ファイルを選択(あるいは除外)するための選択基準を提供する構造の数値フォーマットです。取り得る値は1と2です。1を指定すると、1つのスプール・ファイルを選択するのにそのスプール・ファイルID (つまり完全なジョブ名、スプール・ファイル名、数など)を提供するとAPIに伝えることになりますが、これは今回のアプリケーションでは面倒くさいやり方です。今回のコードでは2を指定し(図―12中のC)、選択基準を提供することをAPIに伝えています。
5番目のフィールドはSelectOffsetという名前で、選択基準までのオフセットを表します。今回はSplfDtaSelect構造のサイズをSplfOffsetフィールドに加えることで、オフセットを計算しています。
最後の6番目のフィールドはNewAttrOffsetという名前で、前述の新しい属性までのオフセットを表しています。今回は、スプール・ファイルを保存した後にその有効期限を変更されたくないので*ZEROを設定しました。
このパラメータ用に初期化する必要のある最後のデータ構造はSplfDtaAttrという名前で、このデータ構造は、スプール・ファイルの操作(WRKSPLF: Work With Spooled Files)、スプール・ファイルの保持(HLDSPLF: Hold Spooled File)、スプール・ファイルの開放(RLSSPLF: Release Spooled File)、スプール・ファイルの削除(DLTSPLF: Delete Spooled File)などのスプール・ファイル用のコマンドの多くで使用される選択パラメータと考え方は似ています。このデータ構造は図―11中のCにあり、これを初期化するコードは図―12中のDにあります。
選択フィールド名については説明不要でしょう。これらのフィールドには名前、汎用名、あるいはその他の適切な特殊値を与えます。(たとえばもしOutqNameフィールドにMYOUTQ*を指定し、OutqLibフィールドに*LIBLを指定した場合、MYOUTQで始まる名前のライブラリ・リスト中にある出力キュー中のすべてのスプール・ファイルが保存されます。) 唯一の例外はStrCrtDateフィールドとEndCrtDateフィールドで、日時の範囲を入力しスプール・ファイルの作成日時に基づいて選択するのに使用します。この両フィールドはいずれも13バイトのフィールドで、日時のデータはCYYMMDDHHMMSSという形式でなければなりません(Cは世紀を表す数字で、0は1900年代、1は2000年代、2は2100年代を表します)。この両フィールドに通常どのような値が保存されていたとしても、特殊値*ALLを指定することができ、それによりAPIに対して選択時にその値を無視するように指示することができます。
図―12のDで、StrCrtDate以外のすべてのフィールドに*ALLを指定しています。StrCrtDateフィールドはこの前に計算した日時フィールドで初期化します。(systeminetwork.com/codeまたはpeterlevy.com)からダウンロード可能なコマンドとプログラムのソースでは、StrCrtDateに挿入される値はREFDATEと REFTIMEという2つのコマンド・パラメータで構築されます。REFDATE(*SAVLIB)が指定された場合、プログラムはオブジェクト記述取り出し(QUSROBJD: Retrieve Object Description) APIを使用してQUSRSYSライブラリから最後に保存された日時を取り出すことでこの値を計算します。日付または時刻がパラメータ中に指定されていた場合は、その値を代わりに使用します。いずれにしろ、QSRSAVO APIがこの基準を使用して、この日時またはそれ以後に作成されたすべてのスプール・ファイルを保存します。QUSRSYSライブラリに最後に保存された日時と、すべてのスプール・ファイルが最後にバックアップされた日時が一致しない場合は、プログラムからこの2つのコマンド・パラメータ中に実際の日時を指定しなければなりません。
プログラムが実行を完了するとスプール・ファイルがメディア(または保存ファイル内)に現れますが、それは*SPLFライブラリ中の*SPLFという名前の出力キューから保存されたように見えます。もちろんこれは見た目だけのことです。というのは、スプール・ファイルを表示させるのにオプション5を使用すると、スプール・ファイルがそれぞれ別の出力キューから来たことがわかるからです。イライラさせられるもう一つの理由は、SAVCHGxxxコマンドとSAVNEWSPLFコマンドを一緒に使用すると、保存されたスプール・ファイルはすべてSAVxxxコマンドで保存した時のようにバラバラにではなく、メディアの最後の部分に保存されるということです。
また、変更のあったオブジェクトと新しいスプール・ファイルをQSRSAVO APIを使用して結合できるかを調べてみましたが、残念なことに結合できませんでした。このAPIが、新しいスプール・ファイルを保存できるのに変更のあったオブジェクトは保存できず、コマンドは変更のあったオブジェクトは保存できるのに新しいスプール・ファイルは保存できないというのはなんとも皮肉な話です。IBMがQSRSAVO APIでREFDATE/REFTIME両パラメータのサポートを追加し、かつ、SAVCHGxxxコマンドでSPLFDTAパラメータのサポートを追加してくれれば皆が楽になるのですが。
コード例をダウンロードしていただくと、SAVxxxコマンドから含めることができたはずのパラメータがたくさん欠けているのにすぐにお気づきになると思います。こうしたのは手を抜いたからではなく、勤務先で作成した稼働中のプログラムを本稿のプログラム例として書き直したからです。このプログラムは実際にはコマンドのフロント・エンドを持たず、本稿には関係のない他の作業も含んでいるからです。
このプログラム例を実運用環境で使用する場合は、コマンドとプログラムをご要望に合わせて拡張したほうが簡単なのでそうしてください。コマンドにパラメータやヘルプ・テキストを追加することもできます。追加のパラメータ値をAPIのユーザー空間に含めるのに必要となる追加のコードは、他のパラメータのコードからコピーして新しい値に合うように変更すれば良いのです。(とても有用なパラメータとしてすぐに頭に思い浮かぶのはENDOPTパラメータとVOLパラメータです。) 変更をしたら私にメールをください。それをウェブ・サイトの新しいバージョンに載せて適切に評価したいと思います。
スプール・ファイルに救いの手を
ご存じではなかったかもしれませんが、導入部分でお話ししました通り、ユーザーはスプール・ファイルをとても重要だと考えています。もしご自分の部門でスプール・ファイルを保存されていないのであれば、是非保存するようにしてください。週末やシステムのフル・バックアップ時にしか保存していないのであれば、SAVCHGxxxとQSRSAVO APIを使用して毎日新しいスプール・ファイルを保存することができます。以後は、万が一重要なスプール・ファイルが誤って削除されたり消去されたりした場合に、週末だけでなく常にバックアップが取られているので、あなたは一躍ヒーローとなるでしょう。