C400

116. エラー・モニター・ハンドラーからのメッセージの検索

CLP の MONMSG のように C/400 でもエラーを監視することができる。
しかしエラーをMONMSG してエラー・メッセージの内容を取り出して
メッセージを出力するにはテクニックが必要となる。

初めに

#include <errno.h>
     :
 volatile _INTRPT_Hndlr_Parms_T ca;  これがエラー・モニター・ハンドラー

  #pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE |  _C2_ALL,
                                       _CTLA_HANDLE)                
      (これが CLPの MONMSG に相当する。)
               :
      (何かエラーが発生 ?! )

  MONMSG:                 
  #pragma disable_handler
     (エラーが発生すればここにジャンプする)

という記述において、まず

  
#pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE |  _C2_ALL,
                                       _CTLA_HANDLE) 

_C2_ALL によってすべての CPFエラーを監視することを意味している。
例外エラーが発生するとエラー・モニター・ハンドラー ca の中に値が入る。
ca とは #include の中に定義されていて

【構造体: _INTRPT_Hndlr_Parms_T】
/* Interrupt handler parameter block */                               
typedef _Packed struct {                                              
  unsigned int    Block_Size;       /* Size of the parameter block    
  _INVFLAGS_T     Tgt_Flags;        /* Target invocation flags        
  char            reserved[8];      /* reserved                       
  _INVPTR         Target;           /* Current target invocation      
  _INVPTR         Source;           /* Source invocation              
  _SPCPTR         Com_Area;         /* Communications area            
  char            Compare_Data[32]; /* Compare Data                   
  char            Msg_Id[7];        /* Message ID                     
  char            reserved1;        /* 1 byte pad                     
  _INTRPT_Mask_T  Mask;             /* Interrupt class mask           
  unsigned int    Msg_Ref_Key;      /* Message reference key          
  unsigned short  Exception_Id;     /* Exception ID                   
  unsigned short  Compare_Data_Len; /* Length of Compare Data         
  char            Signal_Class;     /* Internal signal class          
  char            Priority;         /* Handler priority               
  short           Severity;         /* Message severity               
  char            reserved3[4];     /* reserved                       
  int             Msg_Data_Len;     /* Len of available message data  
   char            Mch_Dep_Data[10]; /* Machine dependent date        
   char            Tgt_Inv_Type;     /*Invocation type (in MIMCHOBS.H)
   _SUSPENDPTR     Tgt_Suspend;      /* Suspend pointer of target     
   char            Ex_Data[48];      /* First 48 bytes of excp. data  
 } _INTRPT_Hndlr_Parms_T;                                             

かなりの十分な情報が収められているように見えるが実は肝心なところが
足らない。
エラー・メッセージを再現しようと思うとエラー・メッセージID は

Exception_Id;

であるが、これも CPFMSG のメッセージ番号だけである。
MSH エラーである場合の取得は不明である。
次に

Ex_Data[48]

がメッセージ・データであるが
「First 48 bytes of excp. data」とあるように、これは
メッセージ・データの最初の 48バイトに過ぎない。
つまりこのデータだけでは発生したエラーの完全なエラー・メッセージ全体を
受け取ることはできない。

となると残る手段は Msg_Ref_Key; (Message reference key=メッセージ参照キー)
である。
メッセージ参照キーとは各メッセージ毎に自動的にシステムによって与えられている
独自の特異な4桁のキー値のことである。

これによって元のメッセージを発したメッセージそのものを取得することができる。
例えばこうだ。

 typedef struct {                   
   Qmh_Rcvpm_RCVM0200_t rcvm0200_t;
   char Msg[3000];                 
 } RCVM0200;                        
 RCVM0200 rcvm0200;                 

 memcpy(msgkey, (void*)&ca.Msg_Ref_Key, 4);                         
 msg_dta_len = sizeof(RCVM0200);                                    
 QMHRCVPM(&rcvm0200,   msg_dta_len, "RCVM0200", "*         ",0,     
           "*ANY      ", msgkey, wait_time,"*SAME     ", &errcode); 

これによってrcvm0200.Msg にはエラー・メッセージの文体が保管されることになる。

次に取得したメッセージをまたこのプログラムの呼出し元に送信しなければならない。
それはこのジョブが対話式であるのか、またはバッチ・ジョブであるかによって
異なってくる。
バッチ・ジョブであれば *SYSOPR (QSYSOPR) にメッセージを送信しないと
操作員の目に触れることはない。
もうひとつ重要なことはバッチ・ジョブの場合は *SYSOPR にメッセージを送信するだけで
よいかというと、それだけではない。
もしこのジョブがさらに上位のプログラム(CLP)などに呼び出されている場合を想定すると

  if(type[0] == '0'){/* バッチ・ジョブ */                            
   /* *SYSOPR にメッセージを送る */                                 
   QMHSNDPM(msgid, "QCPFMSG   QSYS      ", msgdta, msg_dta_len, "*ESCAPE   ",
     "*SYSOPR ", pgm_stk_cnt, "    ", &errcode);                  
 }/* バッチ・ジョブ */     

として *SYSOPR に送信するだけでは済まない。
というのは上位のプログラムは、このプログラムがエラーとして異常終了していないかと
監視している可能性がある。
史実、エラーとなったのであるから *SYSOPR だけに送信したのでは上位の呼び出し元の
プログラムのエラー監視に引っかからなくなってしまう。
従って上位にも送信が必要なのであるが *ESCAPE メッセージを二つ続けて送信することは
できない。
最初の *ESCAPE の送信でこのプログラム自身がエラー終了してしまうからである。
そこで

   if(type[0] == '0'){/* バッチ・ジョブ */                           
   /* *SYSOPR にメッセージを送る */                                
 QMHSNDPM(msgid, "QCPFMSG   QSYS      ", msgdta, msg_dta_len, "*DIA
     "*SYSOPR   ", pgm_stk_cnt, "    ", &errcode);                 
   /*  この *PGMBDY にもメッセージを送る */                        
   QMHSNDPM("CPF9897", "QCPFMSG   QSYS      ", msgdta, msg_dta_len,
        "*ESCAPE   ", "*PGMBDY   ", pgm_stk_cnt, "    ", &errcode);
 }/* バッチ・ジョブ */                                             

のようにして最初には *SYSOPR*DIAG でメッセージして
次に呼出し元にも *ESCAPE で送信する、という二つの送信が必要となる。

これらを統合化して記述すると

  if(type[0] == '0'){/* バッチ・ジョブ */                            
    /* *SYSOPR にメッセージを送る */                                 
    QMHSNDPM(msgid, "QCPFMSG   QSYS      ", msgdta, msg_dta_len, "*DIAG  ", 
    "*SYSOPR   ", pgm_stk_cnt, "    ", &errcode);                  
  }/* バッチ・ジョブ */                                              
  /*  この *PGMBDY にメッセージを送る */                             
  QMHSNDPM("CPF9897", "QCPFMSG   QSYS      ", msgdta, msg_dta_len,   
       "*ESCAPE   ", "*PGMBDY   ", pgm_stk_cnt, "    ", &errcode); 

という記述になる。
現在のジョブがバッチ・ジョブなのか、それとも対話式のジョブであるかを
調べるには RTVJOBA コマンドによる調査が必要である。

RTVJOBA によって受け取るジョブのタイプは 0 がバッチ・ジョブであることを示し
1 が対話式ジョブであることを示している。

以上の説明をひとつのサンプル・ソースにまとめたサンプル・ソースを
以下に示す。

【サンプル・ソース : TESTCHK3】
0001.00 #include                                                       
0002.00 #include                                                      
0003.00 #include                                                      
0004.00 #include                                                       
0005.00 #include                                                      
0006.00 #include                                                    
0007.00 #include                                                    
0008.00 #include                                                     
0009.00 #include                                                     
0010.00 #include "asnet.src/h(common)"  /* POST_LEN, REP_LEN, MAX_USL,ID_LEN */ 
0011.00 #include  /* triml */                                       
0012.00                                                                         
0013.00 #define TRUE         0                                                  
0014.00 #define FALSE       -1                                                  
0015.00   volatile _INTRPT_Hndlr_Parms_T ca;                                    
0016.00 void  PRDCHK(char AutoWeb[1], char Tonakai[1], char eStudio[1],         
0017.00               char DTAARA[20]);                                         
0018.00 #pragma map(PRDCHK, "ASNET.COM/PRDCHKCL")                               
0019.00 #pragma linkage(PRDCHK, OS)                                             
0020.00 void MonitorMSG(_INTRPT_Hndlr_Parms_T ca, char* ref);                   
0021.00 void RtvJobA(char[], char[], char[], char[], char[], char[],            
0022.00              char[], char[], char[], char[], char[], char[], char[],    
0023.00              char[], char[]);                                           
0024.00 #pragma map(RtvJobA, "ASNET.COM/RTVJOBA")                               
0025.00 #pragma linkage(RtvJobA, OS)                                            
0026.00 typedef struct {                                                        
0027.00    int  BYTESPRO;                                                       
0028.00    int  BYTESAVL;                                                       
0029.00    char MSGID[7];                                                       
0030.00    char RESRVD;                                                         
0031.00    char EXCPDATA[100];                                                  
0032.00 } ERRSTRUCTURE;     /* Define the error return structure            */  
0033.00 ERRSTRUCTURE  errcode;/* Error Code Structure for RCVMSG      */        
0034.00                                                                         
0035.00 char ref[132];                                                          
0036.00    char job[10], user[10], jobnbr[6], outq[10], outqlib[10], date[6];   
0037.00    char type[1], prtdev[10], langid[3], cntryid[2], ccsid[5];           
0038.00    char dftccsid[5], cymddate[7], sbmmsgq[10], sbmmsgqlib[10];          
0039.00    char jobid[ID_LEN], ascnbr[6];                                       
0040.00    decimal(5, 0) dftCCSID;                                              
0041.00 void main(void){                                                        
0042.00    #pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE |  _C2_ALL,�  
0043.00                                          _CTLA_HANDLE)                  
0044.00                                                                         
0045.00   printf("** TESTCHK: PRDCHK のテスト **�n");                           
0046.00   getchar();                                                            
0047.00                                                                         
0048.00   /*( ライセンスの検査 )*/                                              
0049.00   strcpy(ref, "LICENSE");                                               
0050.00   PRDCHK("1", "1", " ", "CLIENT    TEST.COM  ");                        
0051.00   printf("NORMAL EOJ.�n");                                              
0052.00   getchar();                                                            
0053.00   return;                                                               
0054.00                                                                         
0055.00   MONMSG:                                                               
0056.00    #pragma disable_handler                                              
0057.00    MonitorMSG(ca, ref);                                                 
0058.00    exit(0);                                                             
0059.00 }                                                                       
0060.00 /***************************************************/                   
0061.00 void MonitorMSG(_INTRPT_Hndlr_Parms_T ca, char* ref)                    
0062.00 /***************************************************/                   
0063.00 {                                                                       
0064.00    char msgid[7], msgkey[4], msg[3000], msgdta[256], reply_msgq[20];    
0065.00    int  msg_dta_len, wait_time = 0, len, pgm_stk_cnt = 1;               
0066.00 typedef struct {                                                        
0067.00    Qmh_Rcvpm_RCVM0200_t rcvm0200_t;                                     
0068.00    char Msg[3000];                                                      
0069.00 } RCVM0200;                                                             
0070.00 RCVM0200 rcvm0200;                                                      
0071.00                                                                         
0072.00    errcode.BYTESPRO = 160;                                              
0073.00    errcode.BYTESAVL  = 0;                                               
0074.00    memcpy(msgkey, (void*)&ca.Msg_Ref_Key, 4);                           
0075.00    msg_dta_len = sizeof(RCVM0200);                                      
0076.00    QMHRCVPM(&rcvm0200,   msg_dta_len, "RCVM0200", "*         ",0,       
0077.00              "*ANY      ", msgkey, wait_time,"*SAME     ", &errcode);   
SOSI       if(strncmp(rcvm0200.rcvm0200_t.Message_Type, "15", 2) == 0){/* エラ  
0079.00      msg_dta_len = rcvm0200.rcvm0200_t.Length_Message_Available;        
0080.00      memcpy(msgdta, rcvm0200.Msg, msg_dta_len);                         
0081.00      msgdta[msg_dta_len] = 0x00;                                        
0082.00      /*( 現在のこのジョブのタイプを調べる )*/                           
0083.00      memcpy(job, "*         ", 10);                                     
0084.00      RtvJobA(job, user, jobnbr, outq, outqlib, date,                    
0085.00         type, prtdev, langid, cntryid, ccsid, dftccsid, cymddate,       
0086.00         sbmmsgq, sbmmsgqlib);                                           
0087.00      if(type[0] == '0'){/* バッチ・ジョブ */                            
0088.00        /* *SYSOPR にメッセージを送る */                                 
0089.00      QMHSNDPM(msgid, "QCPFMSG   QSYS      ", msgdta, msg_dta_len, "*DIA 
0090.00          "*SYSOPR   ", pgm_stk_cnt, "    ", &errcode);                  
0091.00      }/* バッチ・ジョブ */                                              
0092.00      /*  この *PGMBDY にメッセージを送る */                             
0093.00      QMHSNDPM("CPF9897", "QCPFMSG   QSYS      ", msgdta, msg_dta_len,   
0094.00             "*ESCAPE   ", "*PGMBDY   ", pgm_stk_cnt, "    ", &errcode); 
0095.00    }/* エラー・メッセージ */                                            
0096.00    exit(0);                                                             
0097.00 }                                             
【解説】

前述の説明の大半は関数: MonitorMSG に記述されている。
RTVJOBA のソースは紹介されていないが簡単なCLPなので容易に想像はつくはずだ。

このサンプルでは

0050.00   PRDCHK("1", "1", " ", "CLIENT    TEST.COM  ");

*ESCAPE メッセージ CPF9897 を返すことを想定している。
MonitorMSG関数は PRDCHK 関数が戻したエラー・メッセージを取得して
この呼出し元のブログラムにまた戻す、という処理を行う。

MonitorMSG関数はこれまで様々な方法を試してきたが
この関数が最も最新で安定して動作する方法として実用的な考慮の元に
作成されたものであるといえる。

_INTRPT_Hndlr_Parms_T についても IBMマニュアルには詳しい解説は見られないが
今回の解説でご理解は得られたものと思う。

※参考

APIエラーのエラー・コードの構造体は

 typedef struct {                                               
  int  BYTESPRO;                                                
  int  BYTESAVL;                                                
  char MSGID[7];                                                
  char RESRVD;                                                  
  char EXCPDATA[100];                                           
} ERRSTRUCTURE;     /* Define the error return structure      */
ERRSTRUCTURE  errcode;/* Error Code Structure for RCVMSG      */ 

であり、メッセージ・データは char EXCPDATA[100]; という 100バイトの文字であり
_INTRPT_Hndlr_Parms_TEx_Data[48]; に比べればマシであるが
それでも足りない。
それではどれくらいの長さが必要かというと MSGF を WRKMSGF で調べればすぐに
わかるようにメッセージ・データに必要な最大長は 132 である。
IBM が必要な長さを 48バイトとしたり 100バイトとしたのかは、理由は不明であるが
132バイトを確保しておいてくれれば、私達も楽にはなっていただろう。