MC Press Online

Saturday, Jun 24th

Last updateFri, 23 Jun 2017 1pm

You are here: Home ARTICLES Programming APIs The API Corner: The Case of the Missing Stream Files

Programming / APIs

The API Corner: The Case of the Missing Stream Files

Support MC Press - Visit Our Sponsors


Flexible Input, Dazzling Output with IBM i



Click for this Month's

Bookstore Special Deals

The QjoRetrieveJournalEntries API offers flexibility in terms of tracking IFS usage.


Earlier this month, a question was posed over on MIDRANGE-L related to a user having trouble with IFS objects mysteriously disappearing. The question was if there was a way "to journal or otherwise monitor an IFS directory, that would tell us the who, what, when, where, and why of those deletions?" The system can't help very much with the "why" part of the question, but with journaling the system can certainly provide information related to who, what, when, and where. And with that information, hopefully the "who" can help to explain "why."


Today's article will introduce you to the Retrieve Journal Entries (QjoRetrieveJournalEntries) API which, for V5R4, is documented here. The API itself has been available since V4R4. In addition to using this API, you can also access file removal information by using facilities such as the system audit journal, displaying the IFS journal information to an outfile, etc. But as this is the "API Corner," I'm sure you can understand why I've chosen to use the API.


To set the scenario, let's say you have IFS directory /MyPlayDir, which is not currently being journaled. To start journaling, we will create the journal receiver IFS1, create the journal IFSJRN, and then start journaling of the MyPlayDir directory, along with any subdirectories of MyPlayDir. The commands you might use to accomplish these actions are these:






STRJRN OBJ(('/MyPlayDir')) JRN('/qsys.lib/vining.lib/ifsjrn.jrn') +



Having started journaling of the MyPlayDir directory, what we need now is a program that will tell us, for all files removed from MyPlayDir (and subdirectories of MyPlayDir), information such what's shown below using the RPG DSPLY operation code.


DSPLY  Job QPADEV000F/VINING/095249 removed *STMF 

DSPLY  MyMissingFile.txt                          

DSPLY  at 2011-09-04-              

DSPLY  using PGM123                           

DSPLY  Path was /MyPlayDir/MyImbeddedDir           


These results show that job QPADEV000F/VINING/095249, while running program PGM123, removed the stream file MyMissingFile.txt from subdirectory MyImbeddedDir of /MyPlayDir shortly after 11:00 on September 4. As I mentioned, we don't really know "why" MyMissingFile.txt was removed, but we certainly now know quite a bit about the removal itself.


The complete program generating the previous messages, Display File Removal (DSPFRMV), is provided below. While we won't be able to review the entire program this month for space reasons, you will have a working program that you can look at if you are so inclined.


h dftactgrp(*no) bnddir('QC2LE')                                      


dRtvJrnE          pr                  extproc('QjoRetrieveJournalEntries')

d RcvVarPtr                       *   value                           

d LenRcvVar                     10i 0 const                           

d Journal                       20a   const                           

d Format                         8a   const                           

d JrnEtoRtv                  65535a   const options(*omit)            

d QUSEC                               likeds(QUSEC) options(*omit)                  


dGetPath          pr              *   extproc('Qp0lGetPathFromFileID')

d RcvVar                         1                                    

d LenRcvVar                     10u 0 value                           

d FileID                        16a   const                           


dGetErrno         pr              *   extproc('__errno')              


dPathText         s           1000                       

dRcvVarPtr        s               *                      

dLenRcvVar        s             10i 0 inz(10000000)      

dObject           s            640                       

dPath             s          65535                       

dWait             s              1                       

dX                s             10i 0                    

dSavSeqNoPtr      s               *                      

dLstSeqNoChr      s             20                       

dNullPtr          s               *                      

dErrnoPtr         s               *                      

dErrno            s             10i 0 based(ErrnoPtr)    


dRcvVarHdr        ds                  likeds(QJO0100H)   

d                                     based(RcvVarPtr)   


dEntHdrPtr        s               *                                

dEntHdr           ds                  qualified based(EntHdrPtr)   

d Hdr                                 likeds(QJO00JEH)             


dEntSpcDtaPtr     s               *                                

dEntSpcDta        ds                  qualified based(EntSpcDtaPtr)

d Hdr                                 likeds(QJOJEESD)             


dESD_Ptr          s               *                                

dESD_8            ds                  qualified based(ESD_Ptr)     

d ObjFID                        16                                  

d ParentFID                     16                                 

d NameDsp                       10u 0                              

d ObjJID                        10                                 

d ObjType                        7                                  


dObjNameDtaPtr    s               *                                

dObjNameDta       ds                  based(ObjNameDtaPtr)         

d ObjNameLen                    10u 0                              

d ObjNameCCSID                  10i 0                              

d ObjNameCntryID                 2a                                

d ObjNameLangID                  3a                                

d                                3a                                

d ObjName                      640c   ccsid(1200)                  


dJrnEtoRtv        ds         65535    qualified                     

d Hdr                                 likeds(QJOJEJIR)             



d                 s               *                                 

dJrnEtoRtvKeyHdr  ds                  likeds(QJOEFVLR)             

d                                     based(JrnEtoRtvKeyHdrPtr)    



d                 s               *                                 


dGetByStrSeqNo    ds                  qualified                      

d                                     based(JrnEtoRtvKeyHdrDtaPtr)  

dStrSeqNoChr                    20                                  

dStrSeqNoNbr                    20s 0 overlay(StrSeqNoChr :1)       


dGetByJrnType     ds                  qualified                     

d                                     based(JrnEtoRtvKeyHdrDtaPtr)  

d Hdr                                 likeds(QJOJEDK8)              

d JrnTypes                      10    dim(300)                      


dJournal          ds                                                

d JrnName                       10    inz('IFSJRN')                  

d JrnLib                        10    inz('*LIBL')                  


 /copy qsysinc/qrpglesrc,qjournal                                   

 /copy qsysinc/qrpglesrc,qusec                                   




  QUSBPrv = 0;                                                   


  JrnEtoRtv.Hdr.QjoNVLR00 = 2;                                  

  JrnEtoRtvKeyHdrPtr = %addr(JrnEtoRtv) + %size(JrnEtoRtv.Hdr); 


  // Starting sequence number                                   

  JrnEtoRtvKeyHdr.QjoLVLR00 = 32;                               

  JrnEtoRtvKeyHdr.QjoK01 = 2;                                   

  JrnEtoRtvKeyHdr.QjoLOD00 = 20;                                


  JrnEtoRtvKeyHdrDtaPtr = JrnEtoRtvKeyHdrPtr +                  


  GetByStrSeqNo.StrSeqNoChr = '*FIRST';                         

  SavSeqNoPtr = JrnEtoRtvKeyHdrDtaPtr;                       


  // Journal entry type B4                                   

  JrnEtoRtvKeyHdrPtr += JrnEtoRtvKeyHdr.QjoLVLR00;           

  JrnEtoRtvKeyHdr.QjoK01 = 8;                                

  JrnEtoRtvKeyHdr.QjoLOD00 = 14;

  if %rem(JrnEtoRtvKeyHdr.QjoLOD00 :4) = 0;                

     JrnEtoRtvKeyHdr.QjoLVLR00 = %size(JrnEtoRtvKeyHdr) +  



     JrnEtoRtvKeyHdr.QjoLVLR00 =                           

        %size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +

        (4 - %rem((%size(JrnEtoRtvKeyHdr) +                

                   JrnEtoRtvKeyHdr.QjoLOD00) :4));         



  JrnEtoRtvKeyHdrDtaPtr = JrnEtoRtvKeyHdrPtr +                


  GetByJrnType.Hdr.QjoNbrIA00 = 1;                           

  GetByJrnType.JrnTypes(1) = 'B4';                           


  RcvVarPtr = %alloc(LenRcvVar);                             


  dou RcvVarHdr.QjoCH = '0';                                 

      RtvJrnE(RcvVarPtr :LenRcvVar :Journal :'RJNE0100'      

              :JrnEtoRtv :QUSEC);                                    


      EntHdrPtr = RcvVarPtr + RcvVarHdr.QjoOfJE;                     


      for X = 1 to RcvVarHdr.QjoNbrER;                               

          EntSpcDtaPtr = EntHdrPtr + EntHdr.Hdr.QjoDESD;             

          ESD_Ptr = %addr(EntSpcDta.Hdr.QJOESD);                     


          ObjNameDtaPtr = ESD_Ptr + ESD_8.NameDsp;                   

          if ObjNameCCSID = 1200;                                    

             Object = %char(%subst(ObjName :1 :%div(ObjNameLen :2)));



          dsply ('Job ' + %trimr(EntHdr.Hdr.QjoJN02) + '/' +         

                          %trimr(EntHdr.Hdr.QjoUN) + '/' +           

                          %trimr(EntHdr.Hdr.QjoJNbr) +               

                 ' removed ' + %trimr(ESD_8.ObjType));               

          dsply (%subst(Object :1 :52));                             

          dsply ('at ' + EntHdr.Hdr.QjoTS);                         

          dsply ('using ' + EntHdr.Hdr.QjoPgmN);                    


          if GetPath(Path :%size(Path) :ESD_8.ParentFID) = NullPtr; 

             ErrnoPtr = GetErrno();                                 


             PathText = %str(%addr(Path));                          

             dsply ('Path was ' + %subst(PathText :1 :42)) ' ' Wait;



          LstSeqNoChr = EntHdr.Hdr.QjoSNbr;                         

          EntHdrPtr += EntHdr.Hdr.QjoDNJH;                          



      if RcvVarHdr.QjoCH = '1';                                     


            when RcvVarHdr.QjoNbrER = 0;                              

                 // RcvVar not large enough to return even one entry...


                 dsply 'Unable to access journal entries' ' ' Wait;   



            when X > RcvVarHdr.QjoNbrER;                              

                 // Get next set of journal entries                   


                 JrnEtoRtvKeyHdrDtaPtr = SavSeqNoPtr;                 

                 GetByStrSeqNo.StrSeqNoNbr =                          

                   (%dec(LstSeqNoChr :20 :0) + 1);                    







  dealloc RcvVarPtr;     

  *inlr = *on;           





Assuming that your source member name is DSPFRMV, you can compile and run the sample program with the following commands.





As coded, the sample program will DSPLY all deletions journaled to the IFSJRN journal.


The program starts by formatting a call to the QjoRetrieveJournalEntries API. For a retrieve type API, QjoRetrieveJournalEntries is fairly standard in terms of the parameters passed to it. The parameter list is a receiver variable to receive the journal entries, the length of the receiver variable, the qualified name of the journal to be used, the format to be used in returning the journal entries, an omissible parameter defining those journal entries to be retrieved, and an omissible error code parameter.


The fifth parameter, Journal entries to retrieve, provides an extremely flexible method to describe what journal entries are to be returned to our program. By default, all journal entries are returned, but, as we're interested today solely in file removals, we will use this parameter to restrict the returned entries to actions related to removing links from the MyPlayDir (and subdirectories of MyPlayDir) directory.


The Journal entries to retrieve parameter uses keyed variable-length records to define those journal entries to return. The first element of the parameter is a header record that defines how many of these variable-length records we will be using on the call to the API. A definition of this header record is provided in the QSYSINC/QRPGLESRC member QJOURNAL and is used by the DSPFRMV program (with LIKEDS(QJOJEJIR)) to define the data structure JrnEtoRtv (Journal entries to retrieve) with the header record Hdr. The IBM-provided structure, and our use of the structure, is this:


DQJOJEJIR         DS                                                   

D*                                             Qjo JE Jrn Info Retrieve

D QJONVLR00               1      4B 0                                 

D*                                             Num Var Len Rcrds      


dJrnEtoRtv        ds         65535    qualified                    

d Hdr                                 likeds(QJOJEJIR)             


The data structure JrnEtoRtv is arbitrarily defined with a length of 65535, more than sufficient to contain the variable-length records we will be using in our program. Today, we'll use two variable-length records, and DSPFRMV sets the variable JrnEtoRtv.Hdr.QjoNVLR00 to a value of 2.


JrnEtoRtv.Hdr.QjoNVLR00 = 2;                                  


Each variable-length record within the Journal entries to retrieve parameter uses a standard record layout with the fixed-length portion of each record defined by the data structure QJOEFVLR within QSYSINC/QRPGLERC QJOURNAL. This common record layout defines the length of the current variable-length record (used by the API to access the "next" variable length record), the key of the variable-length record (used to identify the format of the data associated with this variable-length record), and the length of the data associated with the key. The IBM-provided structure, and DSPFRMV's use of the structure to define the based structure JrnEtoRtvKeyHdr, is this:


DQJOEFVLR         DS                                                 

D*                                             Qjo JE Fmt Var Len Rcrd

D QJOLVLR00               1      4B 0                                

D*                                             Len Var Len Rcrd      

D QJOK01                  5      8B 0                                

D*                                             Key                   

D QJOLOD00                9     12B 0                                

D*                                             Len Of Data           

D*QJODATA01              13     13                                    

D*                                             Data                  



d                 s               *                                

dJrnEtoRtvKeyHdr  ds                  likeds(QJOEFVLR)             

d                                     based(JrnEtoRtvKeyHdrPtr)    


The JrnEtoRtvKeyHdr data structure is based so that we can use this common definition, by way of the pointer JrnEtoRtvKeyHdrPtr, to populate the Journal entries to retrieve parameter with appropriate variable-length records describing those journal entries to return. The IBM-provided structure defines a commented QJODATA01 variable to indicate where the data associated with the variable-length record starts. The actual layout of the associated data is not provided in structure QJOEFVLR as it is dependent on the key value we use.


To initialize the first variable-length record of JrnEtoRtv, DSPFRMV uses the following:


  JrnEtoRtvKeyHdrPtr = %addr(JrnEtoRtv) + %size(JrnEtoRtv.Hdr); 


  // Starting sequence number                                   

  JrnEtoRtvKeyHdr.QjoLVLR00 = 32;                               

  JrnEtoRtvKeyHdr.QjoK01 = 2;                                   

  JrnEtoRtvKeyHdr.QjoLOD00 = 20;                                


DSPFRMV first sets the pointer JrnEtoRtvKeyHdrPtr to the first byte of JrnEtoRtv following the Hdr record to address where the first variable-length record will begin. The program then initializes the length of the variable-length record to 32 (QjoLVLR00), the key of the variable-length record to 2 (QjoK01), and the length of the data associated with key 2 to 20 (QjoLOD00). In explaining why these values were chosen, we will start with the key value of 2.


The QjoRetrieveJournalEntries API supports a wide variety of key values to control the type of journal entries to be returned. The list of key values available to you can be found in the API documentation. The key value of 2, Starting sequence number, enables you to specify the first journal sequence number that is to be considered for retrieval. You can specify a journal sequence number or the special value *FIRST. *FIRST indicates that all entries should be considered for inclusion based on the specified journal receiver range (which can be controlled with the variable-length record associated with a key value of 1 and defaults to the current journal receiver if not specified). In our case, we're using the special value of *FIRST for the starting sequence number.


I should point out that we do not really need to specify this particular variable-length record at this point in the program. We could have just used one variable-length record (the one we'll be initializing next) for our initial call to the API. The API default, when no key 2 variable-length record is specified, is to start with the first entry available, which is what we're asking for. We may, however, need to use a Starting sequence number key later in the program, so I chose to simply initialize the variable-length record now as opposed to later (and save the location of the starting sequence number using pointer variable SavSeqNoPtr). The reason for possibly needing the Starting sequence number key later is that our receiver variable may, or may not, be large enough for all removal-related journal entries that are available to be returned. If the allocated receiver variable length does prove to be insufficient in size, then we will be using key 2, when calling the API a subsequent time, to resume with the journal entry following the last entry returned on the previous API call.


The value of 20 for QjoLOD00, Length of data, was chosen for the simple reason that, per the API documentation, 20 bytes is the required size of the data associated with key 2.


The value of 32 for QjoLVLR00 was determined using the length of the JrnEtoRtvKeyHdr (12 bytes) plus the length of the associated data (20 bytes). This simple addition may not be sufficient in all cases, though. The API documents that each variable-length record must be aligned on a four-byte boundary and, in the case of key 2, the length of the associated data (20) is already a multiple of four. For this reason, we can simply add the length of the associated data to the size of the header (JrnEtoRtvKeyHdr) and use this value (32) as the length of the variable-length record (which the API then uses to access the next variable-length record). In other cases—for instance, key 8, Journal entry types—the length of the associated data is variable in length. If the length of the associated data does not happen to be a multiple of four, then we need to provide for padding of the associated data to ensure that the next variable-length record is properly aligned. To accomplish this, we can calculate a valid value for QjoLVLR00 using approaches such as this one:


  if %rem(JrnEtoRtvKeyHdr.QjoLOD00 :4) = 0;                

     JrnEtoRtvKeyHdr.QjoLVLR00 = %size(JrnEtoRtvKeyHdr) +  



     JrnEtoRtvKeyHdr.QjoLVLR00 =                           

        %size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +

        (4 - %rem((%size(JrnEtoRtvKeyHdr) +                

                   JrnEtoRtvKeyHdr.QjoLOD00) :4));         



This approach, calculating a valid length, could of course be used rather than hard-coding a value of 32 as was done in DSPFRMV.


Having set the appropriate values for the JrnEtoRtvKeyHdr subfields, DSPFRMV then sets the value of the key associated data using these statements:


  JrnEtoRtvKeyHdrDtaPtr = JrnEtoRtvKeyHdrPtr +                  


  GetByStrSeqNo.StrSeqNoChr = '*FIRST';                         


The pointer JrnEtoRtvKeyHdrDtaPtr is set to the address where the first byte of the associated data value is to be stored by adding the size of the variable-length header to the starting address of the header. As JrnEtoRtvKeyHdrDtaPtr is the basing pointer for data structure GetBGyStrSeqNo, the special value '*FIRST' is then written to GetByStrSeqNo.StrSeqNoChr.


We're now ready to initialize the header of the second variable-length record. The approach taken to set this header is similar to how the header was created for the first variable-length record though this time using a calculated value for QjoLOD00.


  JrnEtoRtvKeyHdrPtr += JrnEtoRtvKeyHdr.QjoLVLR00;           

  JrnEtoRtvKeyHdr.QjoK01 = 8;                                

  JrnEtoRtvKeyHdr.QjoLOD00 = 14;                             

  if %rem(JrnEtoRtvKeyHdr.QjoLOD00 :4) = 0;                

     JrnEtoRtvKeyHdr.QjoLVLR00 = %size(JrnEtoRtvKeyHdr) +  



     JrnEtoRtvKeyHdr.QjoLVLR00 =                           

        %size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +

        (4 - %rem((%size(JrnEtoRtvKeyHdr) +                

                   JrnEtoRtvKeyHdr.QjoLOD00) :4));         



The program…


  1. Increments the pointer variable JrnEtoRtvKeyHdrPtr to address the start of the next variable-length record by adding the length of the current variable-length record to the current value of the JrnEtoRtvKeyHdrPtr pointer
  2. Identifies the key value being used as 8, key value 8 being to filter by journal entry type
  3. Sets the length of the key associated data to 14
  4. Sets the length of the new variable length record to the calculated value of 28 (which is adding two bytes of padding to the end of the associated data in order to align the next variable-length record on a 4-byte boundary)


In the case of key 8, the associated data is a data structure comprised of an integer value indicating how many journal entry types are being specified followed by that number of journal entry types, where each entry type is 10 bytes in length. The IBM-provided definition for this associated data, the use of this definition by DSPFRMV (allowing up to 300 entries to be specified), and the setting of the associated data is as follows:


DQJOJEDK8         DS                                               

D*                                             Qjo JE Data Key 8   

D QJONBRIA00              1      4B 0                              

D*                                             Num In Array        


dGetByJrnType     ds                  qualified                     

d                                     based(JrnEtoRtvKeyHdrDtaPtr)  

d Hdr                                 likeds(QJOJEDK8)              

d JrnTypes                      10    dim(300)                      


  JrnEtoRtvKeyHdrDtaPtr = JrnEtoRtvKeyHdrPtr +               


  GetByJrnType.Hdr.QjoNbrIA00 = 1;                            

  GetByJrnType.JrnTypes(1) = 'B4';                           


To set the key associated data value for key 8, DSPFRMV:


  1. Increments the pointer variable JrEtoRtvKeyHdrDtaPtr to address the first byte of the associated data
  2. Sets the number of journal entry types provided to 1
  3. Sets the first element of the JrnTypes array to the value 'B4', with B4 representing journal entries for removing a link from a parent directory


Having set all necessary values for the Journal entries to retrieve parameter DSPFRMV allocates a receiver variable 10,000,000 bytes in size (a totally arbitrary number, but a number sufficiently large to allow the API to return at least one journal entry) and calls the QjoRetrieveJournalEntries API.


Next month, we'll look at how the DSPFRMV program processes the returned journal entries. As mentioned earlier, the actual processing logic we'll review next month can be found in the program source provided above, so feel free to "skip ahead."


As usual, if you have any API questions, send them to me at This email address is being protected from spambots. You need JavaScript enabled to view it.. I'll see what I can do about answering your burning questions in future columns.

 as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7, V6R1

Bruce Vining

Bruce Vining is president and co-founder of Bruce Vining Services, L.L.C., a firm providing contract programming and consulting services to the System i community ( He began his career in1979 as an IBM Systems Engineer in St. Louis, Missouri, and then transferred to Rochester, Minnesota, in 1985, where he continues to reside. From 1992 until leaving IBM in 2007, Bruce was a member of the System Design Control Group responsible for OS/400 and i5/OS areas such as System APIs, Globalization, and Software Serviceability. He is also the designer of Control Language for Files (CLF).


A frequent speaker and writer, Bruce can be reached at


MC Press books written by Bruce Vining available now on the MC Press Bookstore.


IBM System i APIs at Work IBM System i APIs at Work

Master APIs with this in-depth, example-rich exploration into these powerful tools.

List Price $89.95
Now On Sale