**FREE
/TITLE GETOBJUR - Get users of an object
//--------------------------------------------------------------------
// Given an object and type, this program finds all jobs that have
// locks on the object.  It returns a message suitable for sending
// to the system operator and/or a file, QTEMP/GETOBJUP, which
// could be used to send messages to all interactive users of the
// object, or for some other use.
// This program is called from CL program GETOBJUC.
// It calls the QWCLOBJL (List Object Locks) API.
// Message looks like this:
//   QIWS/QCUSTCDT *FILE is in use by QPAD165916/LENNONS/167740.
// Or this:
//   QIWS/QCUSTCDT *FILE is in use by CUSTLST/LENNONS/167922,
//     DAILYORD/LENNONS/167924, INV_UPD/LENNONS/167923,
//     QPAD165916/LENNONS/167740 plus 1 more.
// Compile: CRTBNDRPG PGM(GETOBJUR) SRCFILE(...) DBGVIEW(*LIST)
//--------------------------------------------------------------------
// 12/00/97 LENNON  Original Writing
// 07/--/2021 Converted to **FREE and Linear module.
//            There are SQL services now that can provide similar
//            infomations, but as far as I know there is no SQL
//            interface to QWCLOBJL.  And there certainly was not in
//            1997. This code probably performs better than existing
//            SQL interfaces. For most use cases performance may not
//            be a consideration.
//--------------------------------------------------------------------
ctl-opt debug option(*nodebugio: *srcstmt)
    dftactgrp(*no) actgrp(*caller)
    main(Main);
dcl-f GETOBJUP usage(*output) usropn block(*yes);
/copy copy_mbrs,USPHDR
//=== CRTUSRSPC (QUSCTRUS) Parameters ================================
dcl-ds *n;
    CusQName char(20);
    CusName char(10) overlay(cusqname) inz('GETOBJUSR');
    CusLib char(10) overlay(cusqname:11) inz('QTEMP');
end-ds;
dcl-s CusAttr char(10) inz('GETOBJUSR ');
dcl-s CusIntSize int(10);
dcl-s CusIntVal char(1) inz(x'00');
dcl-s CusAut char(10) inz('*CHANGE');
dcl-s CusText char(50) inz('GETOBJUSR Command');
//=== Send Pgm Message (QMHSNDPM) Parameters =========================
dcl-s MSNMsgId char(7);
dcl-s MSNFile char(20) inz('QCPFMSG   *LIBL     ');
dcl-s MSNData char(1);
dcl-s MSNLength int(10);
dcl-s MSNType char(10) inz('*ESCAPE');
dcl-s MSNStackE char(10) inz('*CTLBDY');
dcl-s MSNStackC int(10) inz(1);
dcl-s MSNMsgKey char(4);
//=== List Object Locks List Entry Layout ============================
dcl-ds LOLEntry based(loptr);
    LOJobName char(10);
    LOJobUser char(10);
    LOJobNum char(6);
    LOLckState char(10);
    LOLckStus int(10);
    LOLckType int(10);
    LOMemName char(10);
    LOShare char(1);
    LOReserved char(1);
end-ds;
dcl-s LOPtr pointer inz(*null);
dcl-s LOEndPtr pointer inz(*null);
//=== API Error Code Structure =======================================
dcl-ds APIError len(272);
    APIEProv int(10) inz(216) pos(1);
    APIEAvail int(10) inz(0) pos(5);
    APIErrID char(7) pos(9);
    APIErrRsv char(1);
    APIEData char(256);
end-ds;
//=== Misc Field Definitions =========================================
dcl-s LolFmt char(8) inz('OBJL0100');
dcl-s ObjMEM char(10);
dcl-s NumJobs packed(5);
dcl-c MaxJobs const(4); //Maximum jobs for detailed reporting.
// If you increase MaxJobs, increase the message parameter by 30 bytes
// for each additional job.
dcl-ds JobName26;
    SavJobName like(lojobname);
    SavJobUser like(lojobuser);
    SavJobNum like(lojobnum);
end-ds;
dcl-s Jobname28 char(28);
dcl-s JobType char(1);
//=== Parameters for SRTUSRSPC =======================================
// SKeyStart - Array of key starting positions
// SKeyLgth  - Array of key lengths
// SKeyAorD  - Array of ascending/descending sequences
// SKeyType  - Array of key field data types.
dcl-s SKeyStart packed(5) dim(10); // start position
dcl-s SKeyLgth packed(5) dim(10); // lengths
dcl-s SKeyAorD char(1) dim(10); // ascend/descend
dcl-s SKeyType char(1) dim(10); // data type
dcl-s S#Keys packed(3) inz(3);
dcl-s SError char(1);
//=== Program Entry and Parameters ===================================
dcl-proc Main ;
// Input Parameters
//   pObject is object name (10c) + object library (10c)
//   pObjType is *FILE, *DTAARA, etc., standard OS/400 type
//   pObjMem is the member for files, or *NONE if just want users
//           of the file.
//   pMsgYN is *YES to create a message.
//   pFileYN is *YES if file GETOBJUP is to be created in QTEMP
// Output Parameter
//   PMsgFld is where the message gets created.
// ---------------------------------------------------------
    dcl-pi *n extpgm('GETOBJUR');
        pObject char(20);
        pObjType char(10);
        pObjMem char(10);
        pMsgYN char(4);
        pMsgFld char(200);
        pFileYN char(4);
    end-pi;
    //------------------------------------------------------
    dcl-pr ListObjLck extpgm('QWCLOBJL');
        *n char(20);  // CusQName
        *n char(8);   // LolFmt
        *n char(20);  // pObject
        *n char(10);  // pObjType
        *n char(10);  // ObjMem
        *n char(272); // APIError
    end-pr;
    //------------------------------------------------------
    // Gets job type entry for this job.
    // Returns:  JobType has the job type. I=interactive, B=batch, etc.
    dcl-pr GetJobType extpgm('GETJOBTR');
        *n char(26); // JobName26
        *n char(1); // JobType
    end-pr;
    //------------------------------------------------------
    dcl-pr p_srtusrspc extpgm('SRTUSRSPC');
        *n char(10);  // CusName
        *n char(10);  // CusLib
        *n packed(3); // S#keys
        *n packed(5) dim(10); // KeyStart
        *n packed(5) dim(10); // KeyLgth
        *n char(1) dim(10);   // Key A or D
        *n char(1) dim(10);   // SKeyType
        *n char(1); // SError
    end-pr;
    //------------------------------------------------------
    dcl-pr SendMsg extpgm('QMHSNDPM');
        *n char(7);   // MSNMsgId
        *n char(20);  // MSNFile
        *n char(256); // APIEData
        *n int(10);   // MSNLength
        *n char(10);  // MSNType
        *n char(10);  // MSNStackE
        *n int(10);   // MSNStackC
        *n char(4);   // MSNMsgKey
        *n char(272); // APIError
    end-pr;
    //------------------------------------------------------
    dcl-pr UsrSpcCrt extpgm('QUSCRTUS');
        *n char(20); // CusQName
        *n char(10); // CusAttr
        *n int(10);  // CusIntSize
        *n  char(1); // CusIntVal
        *n char(10); // CusAut
        *n char(50); // Custext
    end-pr;
    //------------------------------------------------------
    dcl-pr UsrSpcDlt extpgm('QUSDLTUS');
        *n char(20); // CusQName
        *n char(272); // APIError
    end-pr;
    //------------------------------------------------------
    dcl-pr UsrSpcPointer extpgm('QUSPTRUS');
        *n char(20); // CusQName
        *n pointer;  // UspPtr
    end-pr;

    //=== Code starts here ===========================================
    exsr Initialize;
    //=== List the Object Locks ======================================
    // Object type should be *NONE if not a file.
    if pObjType='*FILE';
        ObjMem=pObjMem;
    else;
        ObjMem='*NONE';
    endif;
    APIEProv=0;
    ListObjLck(
            CusQName:
            LolFmt:
            pObject:
            pObjType:
            ObjMem:
            APIError);
    exsr srtusrspc;
    // === Main Program Loop =========================================
    // Loop through the entries in the list
    exsr NxtJob;
    dow LOPtr <> *NULL;
        if pFileYN='*YES';
            GetJobType(JobName26: JobType);
            exsr WriteRec;
        endif;
        if pMsgYN='*YES';
            exsr BldMsg;
        endif;
        exsr NxtJob;
    enddo;
    // === End of Program ============================================
    if pFileYN='*YES';
        close GETOBJUP;
    endif;
    if pMsgYN='*YES';
        select;
            when NumJobs >= 1 and NumJobs <=MaxJobs;
                pMsgfld=%trimr(pMsgFld) + '.';
            when NumJobs > MaxJobs;
                pMsgfld=%trimr(pMsgFld) + ' plus ' +
                %trim(%editc(NumJobs-MaxJobs:'J'))  +
                ' more.';
            other;
        endsl;
    endif;
    *inlr=*on;
    // === NxtJob ====================================================
    // A job can have more than one lock on an object. We just want a
    // job once.
    // Sets LOPtr to the first entry, or to the next different entry.
    // LOPtr assumed to be *NULL on first call.
        // Returns:  LOPtr=*NULL if no more entries.
        // NumJobs=number of unique jobs so far
    begsr NxtJob;
        select;
            when LOPtr=*NULL;
                if UspLst#Ent > 0;
                    LOPtr=UspPtr+UspLstOfs;
                    LOEndPtr=LOPtr+(UspLst#Ent*UspLstEntSz);
                    NumJobs=0;
                    exsr SavJobInf;
                endif;
            other;
                LOPtr=LOPtr+UspLstEntSz;
                dow LOPtr < LOEndPtr
                    and SavJobName=LOJobName
                    and SavJobUser=LOJobUser
                    and SavJobNum=LOJobNum ;
                    LOPtr=LOPtr+UspLstEntSz;
                enddo;
                if LOPtr < LOEndPtr;
                    exsr SavJobInf;
                else;
                    LOPtr=*NULL;
                endif;
        endsl;
    endsr;
    // === BldMsg ====================================================

    // Adds current job to the message until MaxJobs have been added.
    begsr BldMsg;
        JobName28=%trimr(SavJobName) + '/' +
        %trimr(SavJobUser) + '/' +
        SavJobNum;
        select;
            when NumJobs=1;
                pMsgFld=
                %trimr(%subst(pObject:11:10)) +'/' +
                %trimr(%subst(pObject:1:10)) + ' ' +
                %trimr(pObjType) +
                ' is in use by ' +
                JobName28;
            when NumJobs >= 2 and NumJobs <=MaxJobs;
                pMsgFld=%trimr(pMsgfld) + ', ' +
                JobName28;
        endsl;
    endsr;
    // === SavJobInf =================================================
        // Saves current job indentification information and counts
        // number of unique jobs found.
    // LOPtr must be initialized.
    begsr SavJobInf;
        NumJobs=NumJobs+1;
        SavJobName=LOJobName;
        SavJobUser=LOJobUser;
        SavJobNum=LOJobNum;
    endsr;
    // === WriteRec ==================================================

    // Builds and writes a record to the file GETOBJUP
    // LOPtr must be initialized.
    begsr WriteRec;
        OUJOBNAME=LOJobname;
        OUJOBUSER=LOJobUser;
        OUJOBNUM=LOJobNum;
        OUJOBTYPE=JobType;
        write GETOBJU;
    endsr;
    // === Sort User Space ===========================================

    begsr SrtUsrSpc;
        p_SRTUSRSPC(
            CusName:
            CusLib:
            S#keys:
            SKeyStart:
            SKeyLgth:
            SKeyAorD:
            SKeyType:
            SError);
    endsr;
    // ===============================================================
    // === Initialization ============================================
    begsr Initialize;
        // === Delete the User Space =================================
        APIEProv=%LEN(APIError);
        UsrSpcDlt(CusQName: APIError);
        // If user space doesn't exist, accept message CPF2105, else
        // something nasty is wrong - pass escape message back.
        if APIEAvail <> 0
            and APIErrId <> 'CPF2105';
            APIEProv=0;
            MSNMsgId=APIErrId;
            MSNLength=APIEAvail-16;
            SendMsg(
                MSNMsgId:
                MSNFile:
                APIEData:
                MSNLength:
                MSNType:
                MSNStackE:
                MSNStackC:
                MSNMsgKey:
                APIError);
        endif;
        // === Create User Space =====================================
        // Allow for 100,000 entries, which should be more than enough
        APIEProv = 0;
        CusIntSize=%size(UspHdr) + (%size(LOLentry)*100000);
        UsrSpcCrt(
            CusQName:
            CusAttr:
            CusIntSize:
            CusIntVal:
            CusAut:
            Custext);
        // === Get Pointer to User Space =============================
        UsrSpcPointer(CusQName: UspPtr);
        // === Initialize requested outputs ==========================
        if pFileYN='*YES';
            open GETOBJUP;
        endif;
        if pMsgYN='*YES';
            clear  pMsgFld;
        endif;
        // === Set SRTUSRSPC paremeters ==============================
        SKeyStart(1)=1;
        SKeyStart(2)=11;
        SKeyStart(3)=21;
        SKeyLgth(1)=10;
        SKeyLgth(2)=10;
        SKeyLgth(3)=6;
        SKeyAorD(1)='A';
        SKeyAorD(2)='A';
        SKeyAorD(3)='A';
        SKeyType(1)='C';
        SKeyType(2)='C';
        SKeyType(3)='C';
    endsr;
end-proc;
