Copy Source Members Between Systems Without FTP
May 1, 2013 Bob Cozzi
Moving source code between systems is critical in a multi-system environment. Using save/restore is one way to accomplish this task, but not everyone has save/restore authority. Using FTP is another popular way to move one or more source members between systems, but the downside is that the SRCSEQ and SRCDAT fields are thrown out. Only the SRCDTA field is sent, similar to sending a plain ASCII text file. Maybe I’m late to the party, but I found a better way.
The better way is one of those things that causes me to think that I must be the last person to consider doing it this way. As it turns out, no one else seems to realize it is possible, so here we go.
I had been working with DDM, a fairly old technology that was invented at IBM Rochester. DDM lets high-level language programs connect to database files on other systems, treating remote files as if they were local files. For example, to build a file on your local system that points to a database file on a system identified as CHICAGO on your network, you might run the following command:
CRTDDMF FILE(APPDATA/PICKLES) + RMTFILE(APPDATA/CUSTMAST) + RMTLOCNAME(CHICAGO *IP)
When the local file named PICKLES in library APPDATE is opened in RPG or many system functions, the actual data and file definition of the file named CUSTMAST in library APPDATA residing on the remote IBM i system identified as CHICAGO is accessed. If you declare the file named PICKLES in your RPG IV program, the compiler will pull in the information, field definitions, etc. from the remote file on the CHICAGO system. It’s that transparent.
The RMTFILE parameter may refer to a database file or to a specific member in a database file. To target a specific member, specify the file name in ‘LIBRARY/FILE(MBR)’ format on the RMTFILE parameter. But this restricts access to that specific member. Typically, this is not necessary, because if you target the file rather than the member, all of the OVRDBF and other interfaces that direct you to a member still work. So you can create a DDM file over a remote file and then access a specific member in that remote file using the OVRDBF MBR parameter or other interfaces with similar member choices.
Of course, the DDM connection has to be active for any DDM operation to function, and you have to have a user profile with the same name on both system. Therefore, you have to consider reviewing the DDM TCP/IP attributes. Use the CHGDDMTCPA command to review and change your settings.
The CHGDDMTCPA command can be used to automatically start DDM when the system itself is started. This is normal on most systems, but you need to verify that it is started on your system. Use the STRTCPSVR *DDM command to start DDM on your system if it isn’t already started. After you’ve set it to auto start, you shouldn’t need to start it again.
Next, look at the PWDRQD parameter of CHGDDMTCPA. This is the Password Requirement setting. Depending on which version of IBM i or i5/OS you are running, you will see different options. This parameter sets the lowest level of password security required. Since most shops that have multiple systems do not have identical passwords on every system, the PWDRQD(*VLDONLY) or PWDRQD(*USRID) are popular choices. The *VLDONLY option does not require a password, but if one is sent, it is validated, whereas *USRID only validates that the incoming user ID exists on the system. There are much more secure options available as well. The two options I’ve illustrated here ensure that your user profile exists on the system before allowing a DDM connection to occur. If you also want to validate that the password is correct, you can do that, too.
Copy Source Member To/From Remote
If you haven’t already done so, make sure DDM is started on both systems. The next step is to create a process that can copy source file members.
Using DDM you can process files similar to, but not identical to, local database files. This means that system commands, such as CPYF, will work. One such command is CPYSRCF, which copies one or more source file members between source files. It retains the source file’s sequence numbers and, perhaps more importantly, source statement change dates. We’ve all used these commands before and love them.
Let’s enhance CPYSRCF so that we can use it to copy between source file members on different systems. To do that we need to create a new command and CL program to streamline the process. I’ll call the command CPYSRCRMT (Copy Source File between Remote Systems). It will copy a source file member from one system to another on “our” network. Here’s the syntax diagram:
CPYSRCFMT FROMSRC( library / file *LOCAL | system) TOSRC( library / file *LOCAL | system ) MBROPT(*ADD *REPLACE ) SRCTYPE( *FROMMBR | SEU-Type )
CPYSRCRMT performs the same function as the IBM i CPYSRCF (copy source file) command, but allows you to specify a remote IBM i system on which the source code is copied to, copied from, or both. CPYSRCRMT is one of my favorite commands because it retains all source file statement attributes, such as line change dates and sequence numbers.
The FROMSRC and TOSRC parameters identify the source file to be copied and the system identification where that file exists. The default is *LOCAL, meaning that the FROMSRC or TOSRC file is on the local system, not a remote location. Either can be local or remote, both can be local, or both can be a remote. The FROMMBR and TOMBR identify the member to be copied. This parameter support a full name, generic name, or *ALL. The TOMBR parameter supports *FROMMBR or *FIRST along with a valid member name.
The MBROPT is the REPLACE(*YES or *NO) option, but in CPYF-syntax. The SRCTYPE is used to assign the target member the SEU Source Type. It defaults to *FROMMBR.
The command definition source is as follows:
CPYSRCRMT: CMD PROMPT('COZZI-Copy Remote Src Mbr') PARM KWD(FROMSRC) TYPE(FROMSRC) MIN(1) + PROMPT('From source file') FROMSRC: ELEM TYPE(FROMQUAL) MIN(1) PROMPT('Source file') ELEM TYPE(*CHAR) LEN(64) DFT(*LOCAL) + SPCVAL((*LOCAL)) EXPR(*YES) + INLPMTLEN(25) PROMPT('From System') FROMQUAL: QUAL TYPE(*NAME) MIN(1) EXPR(*YES) QUAL TYPE(*NAME) DFT(*LIBL) SPCVAL((*LIBL) + (*CURLIB)) EXPR(*YES) PROMPT('From library') PARM KWD(TOSRC) TYPE(TOSRC) MIN(1) + PROMPT('To source file') TOSRC: ELEM TYPE(TOQUAL) PROMPT('Source + file') ELEM TYPE(*CHAR) LEN(64) DFT(*LOCAL) + SPCVAL((*LOCAL)) EXPR(*YES) + INLPMTLEN(25) PROMPT('To System') TOQUAL: QUAL TYPE(*NAME) DFT(*FROMSRC) SPCVAL((*FROMSRC)) + EXPR(*YES) QUAL TYPE(*NAME) DFT(*FROMLIB) SPCVAL((*FROMLIB) + (*LIBL) (*CURLIB)) EXPR(*YES) PROMPT('To + library') PARM KWD(FROMMBR) TYPE(*GENERIC) LEN(10) MIN(1) + SPCVAL((*ALL) (*FIRST)) EXPR(*YES) + PROMPT('From member') PARM KWD(TOMBR) TYPE(*NAME) LEN(10) DFT(*FROMMBR) + SPCVAL((*FROMMBR) (*FIRST)) EXPR(*YES) + PROMPT('To member') PARM KWD(MBROPT) TYPE(*CHAR) LEN(10) RSTD(*YES) + DFT(*REPLACE) SPCVAL((*ADD) (*REPLACE)) + EXPR(*YES) PROMPT('Replace or add records') PARM KWD(SRCTYPE) TYPE(*NAME) LEN(10) DFT(*FROMMBR) + SPCVAL((*FROMMBR)) PROMPT('SEU Source Member Type')
To run this command, I only needed a few lines of CL.
CPYRMTSRC: PGM PARM(&FROMSRC &TOSRC &FROMMBR &TOMBR &MBROPT + &SRCTYPE) DCL VAR(&FROMSRC) TYPE(*CHAR) LEN(86) DCL VAR(&FROMFILE) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&FROMSRC 3) DCL VAR(&FROMLIB) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&FROMSRC 13) DCL VAR(&FROMSYS) TYPE(*CHAR) STG(*DEFINED) + LEN(64) DEFVAR(&FROMSRC 23) DCL VAR(&TOSRC) TYPE(*CHAR) LEN(86) DCL VAR(&TOFILE) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&TOSRC 3) DCL VAR(&TOLIB) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&TOSRC 13) DCL VAR(&TOSYS) TYPE(*CHAR) STG(*DEFINED) + LEN(64) DEFVAR(&TOSRC 23) DCL VAR(&FROMMBR) TYPE(*CHAR) LEN(10) DCL VAR(&TOMBR) TYPE(*CHAR) LEN(10) DCL VAR(&MBROPT) TYPE(*CHAR) LEN(10) DCL VAR(&SRCTYPE) TYPE(*CHAR) LEN(10) MONMSG MSGID(CPF0000) IF (&TOFILE *EQ '*FROMSRC') THEN(DO) CHGVAR VAR(&TOFILE) VALUE(&FROMFILE) ENDDO IF ((&TOLIB *EQ ' ') *OR (&TOLIB *EQ '*FROMLIB')) THEN(DO) CHGVAR VAR(&TOLIB) VALUE(&FROMLIB) ENDDO IF (&TOSYS *NE '*LOCAL') THEN(DO) CRTDDMF FILE(QTEMP/COZDDMTO) + RMTFILE(&TOLIB/&TOFILE) + RMTLOCNAME(&TOSYS *IP) CHGVAR VAR(&TOLIB) VALUE('QTEMP') CHGVAR VAR(&TOFILE) VALUE('COZDDMTO') ENDDO IF (&FROMSYS *NE '*LOCAL') THEN(DO) CRTDDMF FILE(QTEMP/COZDDMFROM) + RMTFILE(&FROMLIB/&FROMFILE) + RMTLOCNAME(&FROMSYS *IP) CHGVAR VAR(&FROMLIB) VALUE('QTEMP') CHGVAR VAR(&FROMFILE) VALUE('COZDDMFROM') ENDDO CPYSRCF FROMFILE(&FROMLIB/&FROMFILE) + TOFILE(&TOLIB/&TOFILE) FROMMBR(&FROMMBR) + TOMBR(&TOMBR) MBROPT(&MBROPT) /* SRCTYPE(&SRCTYPE) */ DLTF FILE(QTEMP/COZDDMTO) DLTF FILE(QTEMP/COZDDMFROM) ENDPGM: ENDPGM
This CPYSRCRMT command creates one or two DDM files in QTEMP. Those DDM files reference the FROMSRC and TOSRC parameters. Then a CPYSRCF command is issued using the DDM files in QTEMP as its parameters instead of the source file names you’ve specified. Of course if *LOCAL is used, then the local source file and library names are used by CPYSRCF.
The SRCTYPE parameter on CPYSRCF is commented out in this example because specifying source type become available after IBM i5/OS V5R4. If your version of CPYSRCF contains the SRCTYPE parameter, feel free to uncomment that line of code. In addition, while IBM updated the CHGSRCPF to include a SYSTEM(*LCL | *RMT | *FILETYPE) parameter, they apparently did not do that to the CHGPFM command. So changing source member type using DDM is not possible.
You can use CPYSRCRMT to do any of the following copy functions:
Now you can copy source file members between systems and never have to worry about losing the source sequence or statement change dates.
In my primary network, I have five remote systems and the primary system. I accidentally realized one day when a co-worker was using the CPYSRCRMT command that you can copy between to remote systems. It never occurred to me to try that, but it worked. Sometimes we even surprise ourselves in this industry.
Examples For CPYSRCRMT
Example 1: Copy from local source to remote source
CPYSRCRMT FROMSRC(MYLIB/QRPGLESRC) TOSRC(TESTLIB/*FROMSRC CHICAGO) FROMMBR(WEBAPP2)
This command copies the source member WEBAPP2 from the local system to the QRPGLESRC source file in library TESTLIB on the remote system named CHICAGO.
Example 2: Copy From Remote To Local
CPYSRCRMT FROMSRC(TESTLIB/QRPGLESRC CHICAGO) TOSRC(MYLIB/*FROMSRC) FROMMBR(WEBAPP2)
This command copies the source member WEBAPP2 from the remote system named CHICAGO to the local system.
Example 3: Copy from Remote System to Another Remote System
CPYSRCRMT FROMSRC(TESTLIB/QRPGLESRC CHICAGO) TOSRC(MYLIB/*FROMSRC PHOENIX) FROMMBR(WEBAPP2)
This command copies the source member WEBAPP2 from the remote system named CHICAGO to another remote system named PHOENIX.