Tools

31. CLP で行うシンプルな IFSファイル検査

17. IFSのファイルの存在チェックを行うCHKIFS」 では C/400 ソース・コードによる例を紹介したが、
弊社のユーザーから C/400 の環境のためにコンパイルがうまく行かないとの問い合わせがあった。
そこで作成したのがここに紹介する CLP による CHKIFS である。
CHKIFS の動作原理はシンプルであり、C 関数 (正確には API であるが ) であるopen 関数を使って
IFS のファイルのオープンを試行するだけである。
実は C 関数も ILE環境のごく一部であって、OS 提供のサービス・プログラムに含まれる単なる
プロシージャーのひとつにしか過ぎない。
open 関数 は QSYS/QP0LLIB1 という名前のサービス・プログラムの中に含まれている。
これは API のマニュアルを参照すれば、どのサービス・プログラムであるかを知ることができる。
たとえ API のマニュアルに書かれていなくても、小さな C プログラムを作成してそこにバインドされている
OS の サービス・プログラムの EXPORT プロシージャーを調べればすぐにもわかる。
ただし C 関数の多くのパラメータはポインターによるものが多い。
幸い、最近の OS リリースでは CLP の中でもポインターを使えるようになっているのでCLP によって
C 関数を扱うことができる。
もちろんここに紹介する手法は IBM マニュアルのどこを探しても紹介されていない裏ワザであり、
この方法を理解すれば、 C 関数や API を気軽に扱うことができるようになる。

【 コマンド : CHKIFS 】
---------------------------------------------------------------------------------
0001.00              CMD        PROMPT('IFS 検査 ')                          
0002.00              PARM       KWD(DIR) TYPE(*CHAR) LEN(256) CASE(*MIXED) + 
0003.00                           PROMPT('IFS ストリーム・ファイル ')        
---------------------------------------------------------------------------------
【 CLP : CHKIFSCL 】
----------------------------------------------------------------------------------
0001.00              PGM        PARM(&DIR)                                        
0002.00 /*----------------------------------------------------------------*/      
0003.00 /*   CHKIFS  :  IFS ストリーム・ファイルの存在検査                */      
0004.00 /*                                                                */      
0005.00 /*   SRCTYPE : CLLE                                               */      
0006.00 /*                                                                */      
0007.00 /*   CRTCLMOD  QTEMP/CHKIFSCL SRCFILE(MYSRCLIB/QCLSRC)            */      
0008.00 /*             AUT(*ALL)                                          */      
0009.00 /*   CRTPGM    MYLIB/CHKIFSCL MODULE(QTEMP/CHKIFSCL)              */      
0010.00 /*             BNDSRVPGM(QSYS/QP0LLIB1) ACTGRP(*NEW)              */      
0011.00 /*             AUT(*ALL)                                          */      
0012.00 /*----------------------------------------------------------------*/      
0013.00              DCL        VAR(&MSG) TYPE(*CHAR) LEN(132)                    
0014.00              DCL        VAR(&DIR) TYPE(*CHAR) LEN(256)                    
0015.00              DCL        VAR(&PATH) TYPE(*CHAR) LEN(256)                   
0016.00              DCL        VAR(&PATH_PTR) TYPE(*PTR) ADDRESS(&PATH 0)        
0017.00              DCL        VAR(&RES) TYPE(*INT) LEN(4)                       
0018.00              DCL        VAR(&RES_PTR) TYPE(*PTR) ADDRESS(&RES 0)          
0019.00              DCL        VAR(&TRUE) TYPE(*INT) VALUE(0)                    
0020.00              DCL        VAR(&FALSE) TYPE(*INT) VALUE(-1)                  
0021.00              DCL        VAR(&O_RDONLY) TYPE(*INT) LEN(4) VALUE(1)         
0022.00              DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00')        
0023.00              DCL        VAR(&TYPE) TYPE(*CHAR) LEN(1)                     
0024.00              MONMSG     MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))           
0025.00                                                                           
0026.00              RTVJOBA    TYPE(&TYPE)                                       
0027.00              CHGVAR     VAR(&PATH) VALUE(&DIR *TCAT &NULL)                
0028.00              CALLPRC    PRC('open') PARM((&PATH_PTR *BYVAL) +             
0029.00                           (&O_RDONLY *BYVAL) (*OMIT)) RTNVAL(&RES)        
0030.00              IF         COND(&RES *EQ &FALSE) THEN(DO)                    
0031.00              SNDPGMMSG  MSGID(CPF9897) MSGF(QCPFMSG) +                    
0032.00                           MSGDTA(' ファイル ' *CAT &DIR *TCAT +           
0033.00                           ' が見つかりません。 ') MSGTYPE(*ESCAPE)        
0034.00              ENDDO                                                        
0035.00              RETURN                                                       
0036.00                                                                           
0037.00  ERROR:      RCVMSG     MSGTYPE(*LAST) RMV(*NO) MSG(&MSG)                 
0038.00  SNDMSG:                                                                  
0039.00              IF         COND(&TYPE *EQ '0') THEN(DO)                      
0040.00              SNDPGMMSG  MSG(&MSG) TOMSGQ(*SYSOPR) MSGTYPE(*COMP)          
0041.00              ENDDO                                                        
0042.00              ELSE       CMD(DO)                                           
0043.00              SNDPGMMSG  MSG(&MSG) TOMSGQ(*TOPGMQ) MSGTYPE(*DIAG)          
0044.00              ENDDO                                                        
0045.00              ENDPGM                                                       
----------------------------------------------------------------------------------
【 解説 】
● open 関数とは

最初に open 関数 について紹介しよう。
open 関数は API 解説書の中では

------- 構文 ---------------------------------------------------------------
#include <fcntl.h>

int open(const char* path, int oflag, ...);

として紹介されている。
#include <fcntl.h> とは open 関数のプロトタイプが定義されているヘッダー・ファイルをインクルードするよう
指示している。#include <fcntl.h> の実体は QSYSINC/H(FCNTL) であり、SEU や PDM によって内容を
確認することができる。( 後でこのことは重要になる。)

int という型は 4バイトの整数を意味している。( integer )char* という型は文字列のポインターを意味している。
int oflag, ...... は複数個のフラグを定義することができるが、省略可能であることを意味している。
例えば使用例は次のとおりである。

#include <fcntl.h>
      :
int fildes;
char file[256];

fildes = open(file, O_RDONLY);

ここで O_RDONLY とは読み取り専用として file をオープンすることを意味している。
O_RDONLY は SEU や PDM で QSYSINC/H(FCNTL) を調べれば

#define O_RDONLY   00001        /* Open for reading only           */

として定義されているので O_RDONLY の値は 1 であることがわかる。
open 関数は戻り値として int型の値を戻し、正常にオープンすることができればファイル記述子としての
正の値を戻すが、オープンに失敗すれば -1 を戻す。ファイル記述子は正の値であるが OS によって
自動発生するので正というしかわからない。

● CLP による open 関数の実現

上記の open 関数は CLP では

0028.00              CALLPRC    PRC('open') PARM((&PATH_PTR *BYVAL) +             
0029.00                           (&O_RDONLY *BYVAL) (*OMIT)) RTNVAL(&RES) 

として記述されている。
英小文字を指定するためにプロシージャー名を引用符で囲んで 'open' と記述する。
パス名を示すパラメータに関しては

0015.00              DCL        VAR(&PATH) TYPE(*CHAR) LEN(256)
0016.00              DCL        VAR(&PATH_PTR) TYPE(*PTR) ADDRESS(&PATH 0)

として定義されている。
これは 256バイトの文字変数 &PATH に対して &PATH_PTRTYPE(*PTR) として定義されている
ポインターであり、ADDRESS(&PATH 0) によって &PATH_PTR は変数 &PATH の開始バイト( 0 )を
示すポインターである。RPG で記述する EVAL &PATH_PTR = %ADDR(&PATH) と同じである。
次に open 関数にパラメータとして渡す前に

0022.00              DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00')
   :
0027.00              CHGVAR     VAR(&PATH) VALUE(&DIR *TCAT &NULL)

として &PATH の後ろに NULL ( X'00') を付加しているのは char* という型が最後に NULL で終わる
( NULL-STOP) 型として定義されているからである。またパラメータ : PARM((&PATH_PTR *BYVAL)
定義は char* として &PATH_PTR*BYVAL として値を渡すことを意味している。
IBM の解説ではポインターを渡すときは *BYREF であると解説されているがこれは誤りである。
CALLPRC コマンドのパラメータ・タイプの省略値は *BYREF であるので *BYVAL に明示的に
変更することを忘れてはならない。( *BYREF はめったに使用しない。)

0021.00              DCL        VAR(&O_RDONLY) TYPE(*INT) LEN(4) VALUE(1)
   :
0028.00              CALLPRC    PRC('open') PARM((&PATH_PTR *BYVAL) +
0029.00                           (&O_RDONLY *BYVAL) (*OMIT)) RTNVAL(&RES)

によって TYPE(*INT) の値 1 の変数 : &O_RDONLY(&O_RDONLY *BYVAL) としてパラメータとして
渡しているのがわかる。open 関数の戻り値は

0017.00              DCL        VAR(&RES) TYPE(*INT) LEN(4)
   :
0028.00              CALLPRC    PRC('open') PARM((&PATH_PTR *BYVAL) +
0029.00                           (&O_RDONLY *BYVAL) (*OMIT)) RTNVAL(&RES)

のようにして TYPE(*INT) の int型の変数 &RES として定義されている。
パラメータの場合は int でなくても 4バイトの CHAR に int型のバイナリー・コードを入れても動作するが
戻り値が int型である場合は TYPE(*INT) を戻り値として割り振らないと値が戻ってこないので注意が
必要である。

0020.00              DCL        VAR(&FALSE) TYPE(*INT) VALUE(-1)
   :
0030.00              IF         COND(&RES *EQ &FALSE) THEN(DO)
0031.00              SNDPGMMSG  MSGID(CPF9897) MSGF(QCPFMSG) +
0032.00                           MSGDTA(' ファイル ' *CAT &DIR *TCAT +
0033.00                           ' が見つかりません。 ') MSGTYPE(*ESCAPE)
0034.00              ENDDO

の記述によって戻り値 &RES が -1 であればファイルが見つからないというエラーを MSGTYPE(*ESCAPE)
戻すようにしている。それ以外の戻り値であればファイルは見つかったことになる。
MSGTYPE(*ESCAPE) で戻しているのは このプログラム CHKIFSCL を呼び出すコマンド:CHKIFS
MONMSG を使用することができるようにするためである。例えば、

CHKIFS DIR('/AS400-NET.USR/MYFILE.HTM')
MONMSG  CPF9800  DO
   GOTO ERROR
ENDDO

のような処理を行うことができるようにするためである。

● コンパイル

CRTCLMOD コマンドによってモジュールを作成してから CRTPGM コマンドによって
サービス・プログラム : QSYS/QP0LLIB1 をバインドしているがAPI の解説書(OS:V5R4M0〜) を参照すれば、
その API が含まれているサービス・プログラムがどれであるかが IBM によって公開されている。

DSPSRVPGM  QSYS/QP0LLIB1

によって確かに open 関数が EXPORT されていることを確認されたい。

● CLP による C 関数の利用について

上記に紹介した CHKIFSCL

  • C 関数の 呼び出し方法
  • C 関数のプロトタイプの調べ方
  • CLP による int型とポインターの使用方法

について学習することができたはずである。
CHKIFSCL は、ひとつのサンプルにしか過ぎないが他の多くの C 関数や API をCLP によって
手軽に扱うことができるようになる。