Tuesday, December 10, 2013

SYSTEM( ) as an alternative to QCMDEXC

system qcmdexc error codes

After my post about Capturing QCMDEXC error codes Markus During sent me an alternative method to execute CL commands and retrieve any error message id that might occur.

system() is a runtime C library function that performs the same purpose as the QCMDEXC API. With system() you pass a pointer to the command string and it is executed, no need for the for the command string length.

Below is one way how I could define the prototype for system(), and the H-specifications I would need to be able to create the object.

01 H dftactgrp(*no) bnddir('QC2LE')
02 D SysCmd          PR            10I 0 extproc('system')
03 D                                 *   value
04 D                                       options(*string)

If the system() fails then it sets the _EXCP_MSGID global variable to the value of the error message. This can be imported into a field that can be tested:

01 H dftactgrp(*no) bnddir('QC2LE')
02 D SysCmd          PR            10I 0 extproc('system')
03 D                                 *   value
04 D                                       options(*string)

05 D ErrMsg          S              7    import('_EXCP_MSGID')
    /free
06    SysCmd('CLRPFM TESTFILE') ;

07    if (ErrMsg <> ' ') ;
        .
08    endif ;

As the file TESTFILE exists in the library list the command is successfully excecutes, and the field ErrMsg contains blank as there was no error.

06    SysCmd('CLRPFM XESTFILE') ;

07    if (ErrMsg <> ' ') ;
        .
08    endif ;

This time as the file XESTFILE does not exist, the field ErrMsg contains 'CPF3142' ("File XESTFILE in library *LIBL not found").

In the example below I want to view all of the spool files for the user who is running this program. You should now be familiar with my use of the external data structure for the Program Status Data Structure. If you are not then you must read Externally described Data Structures.

01 H dftactgrp(*no) bnddir('QC2LE')
02 D SysCmd          PR            10I 0 extproc('system')
03 D   CmdString                     *   value
04 D                                       options(*string)

05 D PgmDs         ESDS                  extname(RPG4DS)
06 D                                       qualified

07 D ErrMsg          S              7    import('_EXCP_MSGID')
   /free
08    SysCmd('WRKSPLF SELECT(' + %trimr(PgmDs.User) + 
                             ') OUTPUT(*PRINT)') ;

09    if (ErrMsg <> ' ') ;
        .
10    endif ;

 

This article was written for IBM i 7.1, and it should work with earlier releases too.

22 comments:

  1. One of my favorites

    ReplyDelete
  2. Couple of comments Simon.

    Notice that the function returns a four byte int (10i). The correct way to test for success/failure is to use this. -1 indicates failure as with most C-style functions. e.g.

    if SysCmd('CLRPFM XESTFILE') < 0;
    // Error code here;
    endif ;

    Generally when a function returns status, relying on the reporting area (the msg id) is not normally guaranteed to be a safe way to detect failure.

    It is also a little dangerous to say that no length is needed after having said you pass a pointer to the string. This is only true because Markus used Options(*String) on the prototype. If he had instead passed %Addr( commandString) it would not have worked unless he had manually null terminated commandString (i.e. with hex zeros). The *String option makes a copy of your data, null terminates it, and passes the address of the copy for you. That's why it works.

    If I remember correctly (sorry - don't have time to test) the other potential disadvantage of system() is that the message cannot subsequently be retrieved, so the substitution variables are not available. Not usually a problem and I still use system() a lot but it is not always a good replacement for QCMD...

    ReplyDelete
  3. Sergio Luis Puentes-ValladaresDecember 12, 2013 at 7:47 AM

    Thank Simon, This article is an excellent contribution

    ReplyDelete
  4. You can also use the DB2 stored procedure QSYS2.QCMDEXC

    ReplyDelete
    Replies
    1. Using SQL QCMDEXC SP fails when you attempt to use OVRxxx commands. These commands look for the stack entry and apply the override to the caller of QCMDEXC. In the SQL QCMDEXC SP case its the SQL processing programs not yours. You might as well just stick with one interface for executing commands.

      Delete
  5. There is an issue that I have found with the system function. For some reason, it will always delete the last message from the joblog. This can create support issues when you are unable to see a critical message in a job's joblog.

    I have reverted back to using the good 'ol QCMDEXC since system really doesn't offer anything that it can do and because it doesn't delete messages from the joblog. You can easily retrieve error messages.

    ReplyDelete
  6. I am not sure my concern is valid, but how many "average support programmers" know the system command? I can see a program getting a halt that has a System() op code and programmers saying: "What's that?" Instead of being trendy, why don't just use the QCMDEXC command? I get annoyed when there are these trendy avant-garde solutions in the software when more direct clear code could have been used. Never sacrifice clarity.

    ReplyDelete
    Replies
    1. Respectfully, this is a chicken-egg problem. Support programmers (I am one) will never learn about anything new unless they are introduced to new tools.

      Delete
    2. System() is trendy and avant-garde?

      Interesting descriptive words for this decades-old cross-platform POSIX API that is much more widely used in the world than the platform-specific qcmdexc API.

      If you ask me, it is about as standard as you can get.

      Most of the "average support programmers" I know are perfectly able to Google an unfamiliar API and figure out the basic function in a matter of moments - though I really doubt that is necessary in this case: The name of the API is "system", and the parameter is a string containing an OS command. If, as a developer, I was concerned that the function was still too obscure, I'd just do what any good programmer would do and add a brief comment: "// Execute OS command"

      On IBM i, the system() API is really just a little POSIX-compliant wrapper for QCMDEXC that takes care of calculating the "length" parameter for you. Error detection (just check the return code) and error handling (any error message returned is available in a global environment variable) is also pretty nice using system() vs. QCMDEXC.

      I agree clarity is good, but clarity doesn't mean "only use functions everyone knows by heart". Clarity can also be obtained by good program structure and by good inline documentation.

      Delete
    3. Thank you for responding to my comment on this blog.
      You can also use the C run time function of sleep() or machine interface waittime() instead of DLYJOB. What do I gain by using these out of the mainstream solutions? I am tied of being told that this or that program should only take 4 hours to change, when the program is loaded with all this trendy and avant-garde things that were totally unnecessary. I know for a fact there are other programmers that feel the same way. A program has to be efficient to run on the system and efficient to enhance or support.

      Delete
    4. Let me turn your question around:

      If you know how to use the sleep() API from RPG (I assume we're talking about RPG), why would you code a more-expensive call to qcmdexec in order to indirectly execute the DLYJOB command? Under the covers they both eventually call the same ultimate MI function anyway - but sleep() does it without the expensive additional dynamic call to QCMDEXC.

      sleep() is a standard POSIX API - known the world over. In my opinion that makes it superior (all other things being equal) to the IBM i platform-specific DLYJOB command (and more so if I have to embed that in the equally proprietary qcmdexc call).

      There is reason you see more C-style API calls happening in code: for a decade or so now, the IBM i has been getting more and more open. Code is getting ported from other platforms. Model code what was written in C on Unix or C# on Windows or Java gets ported to IBM i (even to RPG), AIX code (often written in C) can run as is in PASE. Java programmers write code that runs on IBM i. As a result, IBM i programmers are seeing and dealing with more code from other platforms, expanding their skills to include Java, php, Perl, C, and others - and in doing so they discover a lot of functionality they can learn once and use multiple places - like the C-style APIs.

      As I see it, IBM i developers have two basic choices: do nothing to improve their knowledge, and find themselves with stale skills and limited job prospects; or make a little bit of regular effort to stay current.

      Delete
    5. Gary
      Thank you for answering my blog comments. Much of what you say is valid. We might not be as far apart as you might imagine, I once worked with iSeries software where the software pulled data off a cell tower. There was no way that code would have worked without C and MI functions, but there was a compelling reason to use the C and MI functions, In that case, the nature of the application justified the "bells and whistles", but there were thousands of technicians supporting the software.

      Delete
    6. There are many reasons to use one function over another - each has its place. sleep() and usleep() being good examples. Personally I find two advantages to them over a call to QCMDEXC with a DLYJOB. First both are more obvious in intent and second, the overhead of running DLYJOB renders it impractical for short interval polling. In fact the first time I ever used the sleep functions was while assisting a customer who's RPG code was missing poll intervals due to the overhead.

      Certainly you shouldn't use a specific function because it is "trendy" - but it is bad to use any API without thinking about whether it is the best approach.

      By the way - my experience in teaching RPGers has shown me that the number of programmers who are really familiar with QCMDEXC and understand how to detect errors etc. is relatively small. At least the use of system() encourages you to check for errors!

      Delete
    7. Jon/Gary
      Thank you for response. I like you comment "but it is bad to use any API without thinking about whether it is the best approach. " I used the DLYJOB example as an off-the-cuff example, not as some silver bullet.. Use solutions which the application dictates. The iSeries software I saw that was pulling tons of data off a cell phone tower needed to have C , APIs and MI functions.(It was not the NSA).

      I found Gary's comment about "to Google" a little concerning. I amazed the amount of IBM Midrange technology that is on Google, but there is a lot of misinformation out there as well .

      Jon,
      I have purchased your book on "PHP for RPG programmers" and found it was a lot easier to understand the "Java for RPG Programmers." Great job of clarity.

      Delete
    8. @John:

      You mentioned a concern with my earlier comment, in which I said, in part: "Most of the 'average support programmers' [your term] I know are perfectly able to Google an unfamiliar API and figure out the basic function in a matter of moments".

      My point was that when confronted with an unfamiliar API, it only takes a moment to get the documentation on-screen, and that since that documentation is so quickly and readily available (I used a Google search simply as an illustration of one way to access the appropriate online API documentation), it just isn't that big a deal which of the various APIs the original developer selects to perform a given function.

      Personally, I skip the "Google" step altogether - I have the Information Center API Finders bookmarked. But even for those that may not have set up these useful bookmarks, this Google search produced a link to the V7R1 API Finder page on the first hit for me:

      "ibm i v7r1 sleep() api"

      - Gary

      Delete
  7. anyone ever try to use this to call sbmjob with parms, jobd and jobq?

    ReplyDelete
    Replies
    1. You've probably figured it out by now but...
      SysCmd('SBMJOB CMD(DSPFD FILE(' + %TRIM(@LIB) + '/' +
      %TRIM(@FILE) + ') TYPE(*BASATR) OUTPUT(*OUTFILE) +
      OUTFILE(QTEMP/TEMP)' +
      ') JOB(LISTFILE) JOBQ(' + %TRIM(@JOBQ) + ')');

      Delete
  8. Really a good info. Saved a lot of time! Thank you so much

    ReplyDelete
  9. Jon Paris suggestion of
    if SysCmd('CLRPFM XESTFILE') < 0;
    // Error code here;
    endif ;

    I see that in the above case (in the case of an error), function returns 1 instead of -1


    ReplyDelete
  10. In Total Free what would the DCL-pr be for
    D SysCmd PR 10I 0 extproc('system')
    03 D * value
    04 D options(*string)

    ReplyDelete
    Replies
    1. How about...

      dcl-pr SysCmd int(10) extproc('system') ;
      *n pointer value options(*string) ;
      end-pr ;

      Delete
  11. Glad this is still here after all these years!

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.