• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Copy From Stream File FMTOPT(*NOCHK)

    April 13, 2020 Bob Cozzi

    Using SQL frequently, as I do, I tend to look for solutions to problems that can be resolved using the database language (i.e., SQL). Sometimes I have to invent a missing piece of the puzzle. Often that entails writing a User-Defined Function (UDF) for use in SQL, but sometimes a CL command is the better choice.

    Recently I had a situation where what the client calls an “EDI File” was being downloaded using sftp on the IBM server running i. This file is created at a vendor that runs IBM mainframes (an actual mainframe by the way) and the file is created on their system as output from a COBOL program. The reason I know it is COBOL is because when they sent me the file layout, the specs where actually in COBOL.

    For the last 18 years, this file has been retrieved using FTP to the various IBM i platform names every day without a hitch — I’d guess that’s nearly a record for EDI content. However, the days of using FTP are over and sftp is now required by most vendors. Sadly, unlike the FTP tool that runs natively on IBM i, the sftp client is PASE or QShell based. It also is “just a port” of sftp from (probably) IBM’s AIX operating system, which may have been itself ported from FreeBSD. Consequently, sftp does not do CCSID conversions. In fact, the IBM i PASE/QShell sftp doesn’t seem to support ascii/text file transfers, which is when the CCSID conversion could be performed. Effectively every transfer is “BINARY”. Which is not to say that only image or binary files may be transferred; sftp can transfer anything. It just doesn’t automatically do ASCII to EBCDIC translations. This means we have to change the code to handle this situation.

    This story contains code, which you can download here.

    At first, I thought I might be able to use sftp to download the file and then use something like CPYFRMIMPF or CPYFRMSTMF to convert the data to Db2 for i format. But this file had something weird, it had a multiformat, varying length record structure. In the past, we copied it to an interim flat file and then from there to an externally described file. So first we needed to get it into a flat file in the Db2 (or QSYS) file system.

    In many cases, a file downloaded with sftp can still be copied to Db2 for i tables using CPYFRMIMPF file. But CPYFRMIMPF works best with CSV files. Although it does support flat files (it calls them *FIXED) it has some restrictions. In most other cases the CPYFRMIMPF command can be used and can also do the CCSID conversion for you. Sadly, not in my situation.

    I did try using the Copy From Import File (CPYFRMIMPF) command but found that the Field Definition File (FLDDFNFILE) parameter was required when the DTAFMT(*FIXED) parameter is specified. I’m sure someone out there has used that parameter, but not me.

    Then I looked at Copy from Stream File (CPYFRMSTMF) but found that CPDA082 “Must be a source file” was coming up referring to the target file, every time I tried to copy the data.

    In the “old days” the Copy File (CPYF) command seemed to do everything and had a cool FMTOPT(*NOCHK) parameter to “make it work” when it didn’t. But today everyone is working in a heterogeneous environment, and therefore I couldn’t expect the original CPYF command to solve my problem with stream files; so I used the only option left: write my own.

    I decided to write a program in C++ that would do what I wanted. It could have been written in C as well, or even RPG IV for that matter, but I mostly use C++ nowadays since 85 percent of the stuff I code for my products is written in C++.

    My Copy Stream File (CPYSTMF) command copies the data in a stream file to a database file member. CCSID conversions are NOT performed as the requirement is for a “binary copy”. I needed the result to have the same data in it as the downloaded mainframe file. So I open both the input and output as “binary” files so no CCSID conversion is performed.

    Recently I discovered I am one of only a few developers who create their own CL commands. Since I’ve always written my own commands, I just assumed it was a normal, wide-spread practice. To my surprise after a recent survey, I realized I’m more unique in this practice than I had thought. Therefore, I have to assume most of the readers don’t know Command Definition statements. But before I describe them, let’s look a picture of the prompt of this command:

    To create this prompter, the system uses Command Definition Statements. Which are really just a handful of Command themselves that are used to define the parameters of Commands. Commands themselves are just a way to format parameters passed to program.

    There are four basic Command Definition Statements (as they’re called) and only two are needed to create a simple Command.

    • CMD – Command Definition Statement. Defines the prompt text at the top of the screen when the command is prompted. In later years, IBM added most of the CRTCMD parameters to the CMD statement itself-not the name of the program to run, but most others are there.
    • PARM – Defines each Command Parameter. Parameters may be simply parameters, such as a 10-byte name or a 7-digit number, or they can be complex, like a qualified name or list of items. An example of a list of items is the LOG parameter of the SBMJOB command.
    • ELEM – Defines one of the list of elements for a parameter. Each ELEM is like a subfield of a data structure when it is passed to the program.
    • QUAL – Defines a qualify parameter. You’ve all seen qualified parameters, most commands have at least one. It is when you have a 2-, 3-, or more part name that is qualified with the forward slash. The OBJ parameter of DSPOBJD is one example, and the JOB parameter of any command is an example of a 3-part qualified parameter.

    You can also mix these things up and get some cool parameter input results. For example, you might want something like FILE(LIBR/DATAF1 MBRNAME) for your parameter. Where the first part of it is a qualified name, and the second part is a simply 10-byte name.

    As mentioned, you use the CRTCMD to compile Command Definition Statements in a *CMD object. At compile-time you need to specify the PGM (program) parameter. This is the Command Processing Program, or “program to run” when the command is used. That program receives all the parameters defined by the Command.

    My CPYSTMF command (defined below) has five parameters.

    1. FROMSTMT – The name of the stream file (IFS file) to be copied.
    2. FILE – The qualified Db2 file name or the single value *STMF to indicate that you prefer to specify the target file using /QSYS.LIB/MYLIB.LIB/MFILE.FILE syntax.
    3. MBR – The name of the member of the FILE that receives the copied data.
    4. RCDLEN – The record length used when writing data to the target file. If 0 or RCDLEN(*FILE) is specified, the command processing program retrieves the file’s record length using the IBM QDBRTVFD API.
    5. DB2FILE – When you prefer to use /QSYS.LIB syntax for the target file name, this parameter may be used for that purpose. If you specify the target file name here, specify FILE(*STMF).

    There is also a DEP statement in the Command Definition. This statement is used to ensure dependencies between parameters. For example, if you specify FILE(*STMF) then there must be a stream file (STMF) parameter entered, and it cannot be STMF(*FILE).

    CPYSTMF:    CMD        PROMPT('Copy IFS to Db2')
                 PARM       KWD(FROMSTMF) TYPE(*PNAME) LEN(650) MIN(1) +
                              EXPR(*YES) CASE(*MIXED) PROMPT('IFS file +
                              to copy')
                 PARM       KWD(FILE) TYPE(QUAL) SNGVAL((*STMF)) MIN(1) +
                              PROMPT('File to receive data')
     QUAL:       QUAL       TYPE(*NAME) LEN(10) EXPR(*YES)
                 QUAL       TYPE(*NAME) LEN(10) DFT(*LIBL) +
                              SPCVAL((*LIBL) (*CURLIB)) EXPR(*YES) +
                              PROMPT('Library')
                 PARM       KWD(MBR) TYPE(*NAME) LEN(10) DFT(*FIRST) +
                              EXPR(*YES) PROMPT('Member name')
                 PARM       KWD(RCDLEN) TYPE(*INT2) DFT(*FILE) REL(*GT +
                              0) SPCVAL((*FILE 0)) PROMPT('Maximum +
                              record length to write')
                 PARM       KWD(DB2FILE) TYPE(*PNAME) LEN(650) +
                              DFT(*FILE) SPCVAL((*FILE)) EXPR(*YES) +
                              CASE(*MIXED) PROMPT('Db2 file to receive +
                              data')
                 DEP        CTL(&FILE *EQ *STMF) PARM((&DB2FILE *NE *FILE))    
    

    Let look at the C++ code now. The CPYSTMF C++ program opens the stream file as a “binary” file and opens the Db2 for i database file for output in a similar way. The key section of the code is the WHILE loop. In that loop we inspect the data just read from the IFS file. We check if it is at least as long as the target Db2 for i record length, previously retrieved using the QDBRTVFD API or passed in by the user. If it is shorter, I pad the output buffer with x’00’, which may NOT be what you need, so your mileage may vary. To change the pad character to blanks, change the following line in the code as illustrated here:

    From this:

    CODE2

    memset(line+bytesRead,0x00, rcdLen - bytesRead);
    

    To this:

    memset(line+bytesRead,0x40, rcdLen - bytesRead);
    

    We wrote this to write to a file that is made up of character and/or zoned numeric data. Packed data could work as well, however are probably challenges with packed fields. So I recommend having an interim file with the same format as the final destination, but with the packed fields redefined as Zoned decimal and that zoned file being used as the target of this command. After CPYSTMF copies your data in the “zoned” file, use the good old CPYF command with FMTOPT(*MAP) to get it into the final format.

    For the three to five of you out there programming for IBM i using C or C++, the C++ code that runs behind this command is available below. For the last several years, users have been downloading and using my COZTOOLS product at no change. About two years ago, I made COZTOOLS free for the first time in over 30 years; and it’ll stay free forever. In keeping with that philosophy, this new CPYSTMF command shall be added to the next refresh of the COZTOOLS library. But I’m including the source code here for your reference.

               /****************************************************/
               /*  (c) Copyright Cozzi Productions, Inc. 2020      */
               /*  All rights reserved.                            */
               /****************************************************/
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    
    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    
    #include 
    
    using namespace std;
    
    typedef _Packed struct qtf
    {
        char  file[10];
        char  library[10];
    } qualFile_T;
    
    int main(int argc, char* argv[])
    {
    
            std::ifstream  ifsFile;
            std::ofstream  db2File;
            std::string    inFile;
            std::string    outFile;
            std::string    mbrName;
            Qdb_Mbrd0100_t mbrDesc;
            char           overrides = '1';
            char           findMbr   = '1';
            char           stmf[640];
            Qus_EC_t  ec;
            Qdb_Qddfmt_t   fmtInfo;
    
            memset((char*)&fmtInfo,0x00,sizeof(fmtInfo));
    
            char line[4096];
            long  rcdLen   = 0;
            int  bStmf     = 0;
            int  bytesRead = 0;
            qualFile_T     rtnFileLib;
            qualFile_T     dbf;
            char           mbr[10];
    
            if (argc < 3)
            {
              Qp0zLprintf("syntax: copyifs \
               (\'/ifsfolder/file.txt\' \'/QSYS.LIB/mylib.lib/myfile.file/mymbr.mbr\')");
              return 1;
            }
            //  Parameter Sequence:
            // 1 = Source IFS Stream file
            // 2 = Qualified Db2 File name and library
            // 3 = Member Name (blank if none specified)
            // 4 = /QSYS.LIB Db2 File name if FILE(*STMF) is specified.
    
            memset((char*)&mbr, ' ', sizeof(mbr));
            memset((char*)&ec,0x00,sizeof(ec));
            ec.Bytes_Provided = sizeof(ec);
    
            if (argc >= 2) // Stmf Name
            {
              inFile.reserve(640);
              inFile.assign(argv[1],640);
              size_t len = inFile.find_first_of(' ');
              if (len != std::string::npos)
              {
                inFile.erase(len);
              }
            }
            if (argc >= 3)   // Qualified File Library
            {
              outFile.assign(argv[2],20);
            }
            // argc==4 is Member name
            if (argc >= 5)  // max record length to write to target file
            {
                rcdLen = *((short*) argv[4]);
            }
            if (stricmp(outFile.c_str(),"*STMF")==0 && argc >= 5)
            {
              outFile = argv[6];
              bStmf = true;
            }
            else
            {
              memset((char*)&dbf, ' ', sizeof(dbf));
              _CPYBYTES((char*)&dbf, outFile.c_str(), std::min(sizeof(dbf),outFile.length()));
              if (argc >= 4)
              {
                mbrName.assign(argv[3],10);
                _CPYBYTES(mbr,argv[3],std::min(sizeof(mbr),strlen(argv[3])));
              }
            }
    
    
           if (!bStmf)
           {
              if (mbr[0] =='*' || mbr[0]==' ' ||
                  dbf.library[0]=='*' || dbf.library[0]==' ')
              {
                  memset((char*)&ec, 0x00, sizeof(Qus_EC_t));
                  ec.Bytes_Provided = sizeof(Qus_EC_t);
                  memset((char*)&mbrDesc,0x00,sizeof(mbrDesc));
                  if (memicmp(mbr,"*FILE",5)==0)
                  {
                    _CPYBYTES(mbr,dbf.file,sizeof(mbr));
                  }
                  else if (memicmp(mbr,"*ONLY",5)==0 ||
                           memicmp(mbr,"*FIRSTMBR",9)==0 ||
                           mbr[0] == ' ')
                  {
                    _CPYBYTES(mbr,"*FIRST",strlen("*FIRST"));
                  }
                  if (dbf.library[0]==' ')
                  {
                    _CPYBYTES(dbf.library,"*LIBL",strlen("*LIBL"));
                  }
              }
              QUSRMBRD(&mbrDesc, sizeof(mbrDesc),"MBRD0100",&dbf,mbr,
                         &overrides,&ec,&findMbr);
    
    
              if (ec.Bytes_Available == 0)
              {
                 if (rcdLen == 0)
                 {
                   QDBRTVFD((char *) &fmtInfo, sizeof(fmtInfo), 
                            &rtnFileLib, "FILD0200",
                            &dbf, "*FIRST    ", &overrides,
                          "*LCL      ", "*EXT      ", &ec);
                    if (ec.Bytes_Available == 0)
                    {   // GET THE RECORD LENGTH
                       rcdLen = fmtInfo.Qddfrlen;
                    }
                  }
                  char f[11];
                  char l[11];
                  char m[11];
                  memset(f,0x00,sizeof(f));
                  memset(l,0x00,sizeof(l));
                  memset(m,0x00,sizeof(m));
                  _CPYBYTES(f,mbrDesc.Db_File_Name,sizeof(mbrDesc.Db_File_Name));
                  _CPYBYTES(l,mbrDesc.Db_File_Lib,sizeof(mbrDesc.Db_File_Lib));
                  _CPYBYTES(m,mbrDesc.Member_Name,sizeof(mbrDesc.Member_Name));
                  f[::triml(f, ' ')] = 0x00;
                  l[::triml(l, ' ')] = 0x00;
                  m[::triml(m, ' ')] = 0x00;
                  sprintf(stmf,"/QSYS.LIB/%s.lib/%s.file/%s.mbr",l,f,m);
                  outFile = stmf;
               }
           }
    
           ifsFile.open(inFile.c_str(),ios_base::_occsid | std::ifstream::in); 
    
           std::transform(outFile.begin(), outFile.end(), outFile.begin(), ::toupper);
           db2File.open(outFile.c_str(), ios::out|ios::binary);
    
           if (ifsFile.is_open()) // if is open...
           {
             while (!ifsFile.eof())
             {
                ifsFile.getline(line,sizeof(line)-1);
                bytesRead = ifsFile.gcount();
                if (bytesRead > 0)
                {
                  if (rcdLen > 0 && rcdLen < bytesRead)
                  {
                     bytesRead = rcdLen;
                  }
                  else
                  {
                     bytesRead--;
                     if (rcdLen > 0)
                     {  // pad the line to the full requested record length
                       if ( bytesRead < rcdLen)
                       {
                         memset(line+bytesRead,0x00, rcdLen - bytesRead);
                       }
                       bytesRead = rcdLen;
                     }
                  }
                  db2File.write( line, bytesRead );
                }
             }
           }
           ifsFile.close();
           db2File.close();
           return 0;
    }
    

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: 400guru, C, CCSID, COBOL, FHG, Four Hundred Guru, IBM i, PASE, qshell, SFTP, SQL, UDF, User-Defined Function

    Sponsored by
    New Generation Software

    FREE Webinar:

    Creating Great Data for Enterprise AI

    Enterprise AI relies on many data sources and types, but every AI project needs a data quality, governance, and security plan.

    Wherever and however you want to analyze your data, adopting modern ETL and BI software like NGS-IQ is a great way to support your effort.

    Webinar: June 26, 2025

    RSVP today.

    www.ngsi.com – 800-824-1220

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    The X Factor: Home Base Where Is The Power Systems-IBM i Stimulus Package?

    One thought on “Guru: Copy From Stream File FMTOPT(*NOCHK)”

    • Kevin Wright says:
      April 13, 2020 at 7:54 pm

      The #includes in your C++ source didn’t include what was supposed to be included. That is, they appear not to be inclusive.

      Reply

    Leave a Reply Cancel reply

TFH Volume: 30 Issue: 24

This Issue Sponsored By

  • ProData Computer Services
  • Maxava
  • New Generation Software
  • 400School.com
  • Raz-Lee Security

Table of Contents

  • Hacking IBM i: Penetration Testing Gains Popularity
  • Where Is The Power Systems-IBM i Stimulus Package?
  • Guru: Copy From Stream File FMTOPT(*NOCHK)
  • The X Factor: Home Base
  • IT Starts To Feel The Impact Of The Great Infection

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • FAX/400 And CICS For i Are Dead. What Will IBM Kill Next?
  • Fresche Overhauls X-Analysis With Web UI, AI Smarts
  • Is It Time To Add The Rust Programming Language To IBM i?
  • Is IBM Going To Raise Prices On Power10 Expert Care?
  • IBM i PTF Guide, Volume 27, Number 20
  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle