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:
CRTJRNRCV JRNRCV(IFS1)
CRTJRN JRN(IFSJRN) JRNRCV(IFS1)
STRJRN OBJ(('/MyPlayDir')) JRN('/qsys.lib/vining.lib/ifsjrn.jrn') +
SUBTREE(*ALL)
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-11.11.14.734704
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)
dJrnEtoRtvKeyHdrPtr...
d s *
dJrnEtoRtvKeyHdr ds likeds(QJOEFVLR)
d based(JrnEtoRtvKeyHdrPtr)
dJrnEtoRtvKeyHdrDtaPtr...
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
/free
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 +
%size(JrnEtoRtvKeyHdr);
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.QjoLOD00;
else;
JrnEtoRtvKeyHdr.QjoLVLR00 =
%size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +
(4 - %rem((%size(JrnEtoRtvKeyHdr) +
JrnEtoRtvKeyHdr.QjoLOD00) :4));
endif;
JrnEtoRtvKeyHdrDtaPtr = JrnEtoRtvKeyHdrPtr +
%size(JrnEtoRtvKeyHdr);
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)));
endif;
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();
else;
PathText = %str(%addr(Path));
dsply ('Path was ' + %subst(PathText :1 :42)) ' ' Wait;
endif;
LstSeqNoChr = EntHdr.Hdr.QjoSNbr;
EntHdrPtr += EntHdr.Hdr.QjoDNJH;
endfor;
if RcvVarHdr.QjoCH = '1';
select;
when RcvVarHdr.QjoNbrER = 0;
// RcvVar not large enough to return even one entry...
dsply 'Unable to access journal entries' ' ' Wait;
leave;
when X > RcvVarHdr.QjoNbrER;
// Get next set of journal entries
JrnEtoRtvKeyHdrDtaPtr = SavSeqNoPtr;
GetByStrSeqNo.StrSeqNoNbr =
(%dec(LstSeqNoChr :20 :0) + 1);
iter;
other;
endsl;
endif;
enddo;
dealloc RcvVarPtr;
*inlr = *on;
return;
/end-free
Assuming that your source member name is DSPFRMV, you can compile and run the sample program with the following commands.
CRTBNDRPG PGM(DSPFRMV)
CALL DSPFRMV
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
dJrnEtoRtvKeyHdrPtr...
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.QjoLOD00;
else;
JrnEtoRtvKeyHdr.QjoLVLR00 =
%size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +
(4 - %rem((%size(JrnEtoRtvKeyHdr) +
JrnEtoRtvKeyHdr.QjoLOD00) :4));
endif;
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 +
%size(JrnEtoRtvKeyHdr);
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.QjoLOD00;
else;
JrnEtoRtvKeyHdr.QjoLVLR00 =
%size(JrnEtoRtvKeyHdr) + JrnEtoRtvKeyHdr.QjoLOD00 +
(4 - %rem((%size(JrnEtoRtvKeyHdr) +
JrnEtoRtvKeyHdr.QjoLOD00) :4));
endif;
The program…
- 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
- Identifies the key value being used as 8, key value 8 being to filter by journal entry type
- Sets the length of the key associated data to 14
- 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 +
%size(JrnEtoRtvKeyHdr);
GetByJrnType.Hdr.QjoNbrIA00 = 1;
GetByJrnType.JrnTypes(1) = 'B4';
To set the key associated data value for key 8, DSPFRMV:
- Increments the pointer variable JrEtoRtvKeyHdrDtaPtr to address the first byte of the associated data
- Sets the number of journal entry types provided to 1
- 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
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7, V6R1
LATEST COMMENTS
MC Press Online