iSeries & System i

#2 Tips & Tricks - Table of Contents #4

Get attributes for an IFS object
Generate a screen print like "Print-Key"- Function
Regex in RPG
Configuring SMTP E-Mail for SNA distribution
Guide to block mail relay and spamming
Problems with MSF and SMTP server
System Reference Codes at IPL time
Backing up the IFS system
Null-terminated data - to do or not to do
The reason for semicolon on /Free specs?
Debug doesn't show values for all fields in a file
Converting Numeric Variables to Zero-Filled Character in Free-Format RPG
Service Programs -- The Chicken and The Egg
Finding Commands and Menus
Sending Spooled Files To Another System
Navigate Multiple Client Access Sessions More Quickly And Easily
How to "squeeze" a field of spaces
Get a command line from SysRq 3
Get Host by Name & Get Host by Address
CHKOBJ for an IFS file
Debug a batch program
How File Overrides Really Work in ILE
File Overrides Demystified
Mean Absolute Deviation (MAD)
V5R3: Changed commands
Quick Reference - Build-in Functions



Get attributes for an IFS object

Q:	Is there a way I can get the attributes (date/time created/modified, file size) for
an IFS object in a CL or RPG/IV program? I have a batch file running on an NT server that
extracts some data from another server (Novell) to a directory on the AS/400. On the
AS/400 side, I want to ensure I have a file created "today" before I try to process it.

A:	Yes, you can use the stat() API from the UNIX-Type APIs manual to get this
information. The time returned is in GMT, I believe.  You may have to adjust it for your
timezone.

D********************************************************************** D* File Information Structure (stat) D* D* struct stat { D* mode_t st_mode; /* File mode */ D* ino_t st_ino; /* File serial number */ D* nlink_t st_nlink; /* Number of links */ D* uid_t st_uid; /* User ID of the owner of file */ D* gid_t st_gid; /* Group ID of the group of file */ D* off_t st_size; /* For regular files, the file D* * size in bytes */ D* time_t st_atime; /* Time of last access */ D* time_t st_mtime; /* Time of last data modification */ D* time_t st_ctime; /* Time of last file status change */ D* dev_t st_dev; /* ID of device containing file */ D* size_t st_blksize; /* Size of a block of the file */ D* unsigned long st_allocsize; /* Allocation size of the file */ D* qp0l_objtype_t st_objtype; /* AS/400 object type */ D* unsigned short st_codepage; /* Object data codepage */ D* char st_reserved1[66]; /* Reserved */ D* }; D* D statds DS D st_mode 10U 0 D st_ino 10U 0 D st_nlink 5U 0 D st_pad 2A D st_uid 10U 0 D st_gid 10U 0 D st_size 10I 0 D st_atime 10I 0 D st_mtime 10I 0 D st_ctime 10I 0 D st_dev 10U 0 D st_blksize 10U 0 D st_alctize 10U 0 D st_objtype 12A D st_codepag 5U 0 D st_resv11 67A D*-------------------------------------------------------------------- D* Get File Information D* D* int stat(const char *path, struct stat *buf) D*-------------------------------------------------------------------- D stat PR 10I 0 ExtProc('stat') D path * value D buf * value D Path S 640A D Msg S 52A D ModTime S Z INZ(z'1970-01-01-00.00.00.00000') c eval Path = '/QOpenSys/usr/myfile.txt'+x'00' c if stat(%addr(Path): %addr(statds)) <> 0 c eval Msg = 'stat() failed!' c dsply Msg c eval *inlr = *on c return c endif c adddur st_mtime:*s ModTime c 'Changed' dsply ModTime c eval *inlr = *on Thanks to Scott Klement

Back

Generate a screen print like "Print-Key"- Function

Here's a DSM example:

**-- Header specifications: --------------------------------** H BndDir( 'QC2LE' : 'QSNAPI' ) Option( *SrcStmt ) **-- Global variables: -------------------------------------** D InpBufHdl s 10i 0 D InpDtaPtr s * **-- Parameters: -------------------------------------------** D Parm Ds D Row 10i 0 D Col 10i 0 D NbrBytRtn 10i 0 D Screen 3564a **-- Prototype for DSM API's: ------------------------------** D GetCsrAdr Pr 10i 0 ExtProc( 'QsnGetCsrAdr' ) D Row 10i 0 D Col 10i 0 D LlvEnvHdl 10i 0 Const Options( *Omit ) D ApiError 1024a Options( *Omit: *VarSize ) ** D CrtInpBuf Pr 10i 0 ExtProc( 'QsnCrtInpBuf' ) D InpBufSiz 10i 0 Const D BufIncSiz 10i 0 Const Options( *Omit ) D BufMaxSiz 10i 0 Const Options( *Omit ) D InpBufHdl 10i 0 Options( *Omit ) D ApiError 1024a Options( *Omit: *VarSize ) ** D ReadScr Pr 10i 0 ExtProc( 'QsnReadScr' ) D NbrBytRead 10i 0 Options( *Omit ) D InpBufHdl 10i 0 Const Options( *Omit ) D CmdBufHdl 10i 0 Const Options( *Omit ) D LlvEnvHdl 10i 0 Options( *Omit ) D ApiError 1024a Options( *Omit: *VarSize ) ** D RtvDta Pr * ExtProc( 'QsnRtvDta' ) D InpBufHdl 10i 0 Const D InpDtaPtr * Options( *Omit ) D ApiError 1024a Options( *Omit: *VarSize ) ** D Beep Pr 10i 0 ExtProc( 'QsnBeep' ) D CmdBufHdl 10i 0 Const Options( *Omit ) D LlvEnvHdl 10i 0 Const Options( *Omit ) D ApiError 1024a Options( *Omit: *VarSize ) ** D DltBuf Pr 10I 0 ExtProc( 'QsnDltBuf' ) D BufferHdl 10I 0 Const D ApiError 1024a Options( *Omit: *VarSize ) ** D MemCpy Pr * ExtProc( 'memcpy' ) D pOutMem * Value D pInpMem * Value D InpMemSiz 10u 0 Value ** **--Mainline: ----------------------------------------------** ** C Eval InpBufHdl = CrtInpBuf( 27 * 132 C : *Omit C : *Omit C : *Omit C : *Omit ) ** C CallP GetCsrAdr( Row C : Col C : *Omit C : *Omit ) ** C Eval NbrBytRtn = ReadScr( *Omit C : InpBufHdl C : *Omit C : *Omit C : *Omit ) ** C Eval InpDtaPtr = RtvDta( InpBufHdl C : *Omit C : *Omit ) ** C CallP MemCpy( %Addr( Screen ) C : InpDtaPtr C : NbrBytRtn ) ** C CallP Beep( *Omit C : *Omit C : *Omit ) ** C CallP DltBuf( InpBufHdl: *Omit ) ** C Return Thanks to Carsten Flensburg

Back

Regex in RPG

Q:	Is there a way to bind in the regex functions in rpg?
Similar to the way you would use the other c functions?

A:	Okay...  I converted the example from the "ILE C for AS/400 Runtime
reference" into RPG (more or less).    The difference being that I used
diagnostic messages and escape messages instead of printf-type messages.

H BNDDIR('QC2LE') DFTACTGRP(*NO) D regex_t DS align D re_nsub 10I 0 D re_comp * D re_cflags 10I 0 D re_erroff 10I 0 D re_len 10I 0 D re_ucoll 10I 0 dim(2) D re_lsub * DIM(9) D re_esub * DIM(9) D re_map 256A D re_shift 5I 0 D re_dbcs 5I 0 D regmatch_t DS occurs(2) align D rm_so 10I 0 D rm_ss 5I 0 D rm_eo 10I 0 D rm_es 5I 0 D regcomp PR 10I 0 extproc('regcomp') D preg * value D pattern * value D cflags 10I 0 value D regexec PR 10I 0 extproc('regexec') D preg * value D string * value D nmatch 10U 0 value d pmatch * value D eflags 10I 0 value D regerror PR 10U 0 extproc('regerror') D errcode 10I 0 value D preg * value D errbuf * value D errbuf_size 10I 0 value D regfree PR extproc('regfree') D preg * value D DiagMsg PR D peMsgTxt 256A Const D EscapeMsg PR D peMsgTxt 256A Const D preg S * D pmatch S * D string S 50A D len S 10I 0 D rc S 10I 0 D nmatch S 10U 0 INZ(2) D Msg S 50A D Buf S 256A D pattern S 50A c eval *inlr = *on c* Set example values c eval string = 'a very simple simple ' + c 'simple string' + x'00' c eval pattern = '\(sim[a-z]le\) \1' + x'00' c* Initialize pointers c 1 occur regmatch_t c eval preg = %addr(regex_t) c eval pmatch = %addr(regmatch_t) C* Compile RE c eval rc=regcomp(preg:%addr(pattern):0) c if rc <> 0 c callp regerror(rc: preg: %addr(buf): 256) c callp EscapeMsg('regcomp() failed with: ' + c %str(%addr(buf))) c endif C* Execute RE c eval rc = regexec(preg: %addr(string): c nmatch: pmatch: 0) c if rc <> 0 c callp regerror(rc: preg: %addr(buf): 256) c callp regfree(preg) c callp EscapeMsg('regexec() failed with: ' + c %str(%addr(buf))) c endif C* Show results: c 1 occur regmatch_t c eval len = rm_eo - rm_so c eval rm_so = rm_so + 1 c callp DiagMsg('With the whole expression, ' + c 'a matched substring "' + c %subst(string: rm_so: len) + c '" is found at position ' + c %trim( %editc(rm_so: 'Z') ) + c ' to ' + %trim( %editc(rm_eo: 'Z') )) c 2 occur regmatch_t c eval len = rm_eo - rm_so c eval rm_so = rm_so + 1 c callp DiagMsg('With the sub-expression, ' + c 'a matched substring "' + c %subst(string: rm_so: len) + c '" is found at position ' + c %trim( %editc(rm_so: 'Z') ) + c ' to ' + %trim( %editc(rm_eo: 'Z') )) c callp regfree(preg) c return P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P* This puts a diagnostic message into the job log P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P DiagMsg B D DiagMsg PI D peMsgTxt 256A Const D SndPgmMsg PR ExtPgm('QMHSNDPM') D MessageID 7A Const D QualMsgF 20A Const D MsgData 256A Const D MsgDtaLen 10I 0 Const D MsgType 10A Const D CallStkEnt 10A Const D CallStkCnt 10I 0 Const D MessageKey 4A D ErrorCode 1A D dsEC DS D dsECBytesP 1 4I 0 inz(256) D dsECBytesA 5 8I 0 inz(0) D dsECMsgID 9 15 D dsECReserv 16 16 D dsECMsgDta 17 256 D wkMsgLen S 10I 0 D wkTheKey S 4A c ' ' checkr peMsgTxt wkMsgLen c if wkMsgLen<1 c return c endif c callp SndPgmMsg('CPF9897': 'QCPFMSG *LIBL': c peMsgTxt: wkMsgLen: '*DIAG': c '*': 0: wkTheKey: dsEC) c return P E P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P* This ends the program with an escape message P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P EscapeMsg B D EscapeMsg PI D peMsgTxt 256A Const D SndPgmMsg PR ExtPgm('QMHSNDPM') D MessageID 7A Const D QualMsgF 20A Const D MsgData 256A Const D MsgDtaLen 10I 0 Const D MsgType 10A Const D CallStkEnt 10A Const D CallStkCnt 10I 0 Const D MessageKey 4A D ErrorCode 1A D dsEC DS D dsECBytesP 1 4I 0 inz(256) D dsECBytesA 5 8I 0 inz(0) D dsECMsgID 9 15 D dsECReserv 16 16 D dsECMsgDta 17 256 D wkMsgLen S 10I 0 D wkTheKey S 4A c ' ' checkr peMsgTxt wkMsgLen c if wkMsgLen<1 c return c endif c callp SndPgmMsg('CPF9898': 'QCPFMSG *LIBL': c peMsgTxt: wkMsgLen: '*ESCAPE': c '*PGMBDY': 1: wkTheKey: dsEC) c return P E Thanks to Scott Klement Seems to be some troubles with Scott's example for people outside the USA. This could be a help:

Franz wrote:
I tried to use the C-function regex in RPG. For this, there is a sourcefile in C in the "C / C++ Programmersguide" and a RPG-program at "http://www.think400.dk/adhoc_3.htm". With both programs, I get the message "Failed to match". First I think, its a problem of the character set. But i can't find out.
Barbara Morris wrote:
I think you're right that it's a character set problem. I put that source into a CCSID(37) file and it ran as expected. I copied it into a CCSID(273) file and changed my job to 273, and compiled it again, and it got the "Failed to match" error when it ran. When I corrected the CCSID(273) source so that the backslash and square-bracket characters \[] were correct, and recompiled, the program ran fine.
Thanks to both Franz and Barbara
Tutorials for Regular Expressions:
Link I Link II Utility for Regular Expressions

Thanks to Buck Calabro

Back

Configuring SMTP E-Mail for SNA distribution

Add Host Table Entry for SMTP Gateway:
Option 10 from CFGTCP - add a host table entry for the SMTP gateway and
call it something like ‘SMTPGATE’ and then CHGSMTPA to add the SMTP gateway
as the router (MAILROUTER parm) and change firewall to 'YES'.


Change the AS/400 TCP/IP Domain:
Option 12 from CFGTCP. Make sure your Host Name is the AS/400 name (e.g.
MYAS400 or S103456) and the domain name conforms to your network standard B
e.g mycompany.com or London.yc (if you have your own intranet domain)

You may need a DNS server ? if you don't have one, use your ISP’s one.


Add a Directory Entry as Route to SMTP Gateway:
Add a new entry to the system distribution directory to define the Route to
SMTP gateway. The User ID and Address of this entry is later specified with
the SMPTPRTE parameter for the Change Distribution Attributes (CHGDSTA)
command.


The directory entry must specify the following:
The User ID and Address (USRID) can be anything, but should indicate that
this entry refers to Internet or SMTP users. The suggested entry is:

USRID(INTERNET SMTPRTE)

Users do not need to know this address, but you must specify the same value
when changing the Distribution Attributes in Configure Route to SMTP
Gateway in Distribution Attributes later.

The  User entry description (USRD) again can be anything, but should
indicate  that this entry refers to Internet or SMTP users. Suggestion:

USRD('User ID to route Internet addresses')

As  User profile name (USER), \NONE should be specified.

The  System name (SYSNAME) can be anything different to this system's name
or other system in the SNADS network (for example, you might choose:
INTERNET).

The  Network User ID (NETUSRID) must be the same as the User ID and Address
(that is, specify USRID).

The  Mail Service Level (MSFSRVLVL) must be \USRIDX. (that is, option 1
when using the Work with Directory Entries (WRKDIRE) command.

The Preferred address (PREFADR) must be:
  Field Name = NETUSRID,
  Product ID =  *IBM,
  Address Type = ATCONTXT.


Configure Route to SMTP Gateway in Distribution Attributes:
The user ID and address for the system distribution directory entry added
in AConfigure Route to SMTP Gateway in Distribution Attributes@ now needs
to be specified with the Change Distribution Attributes (CHGDSTA) command.
In any AS/400 command line, type CHGDSTA and press F4.

Change Distribution Attributes (CHGDSTA)
 Keep recipients . . . . . . . . *BCC
 Use MSF for local . . . . . . . *NO

Route to SMTP gateway:
 User ID . . . . . . . . . . . INTERNET
 Address . . . . . . . . . .  SMTPRTE

STRTCPSVR *SMTP

Authorities: You may need to GRTOBJAUT to the mail server framework objects
in QSYS. (Try QMSF* and MSF*)


SNDDST to send a document or file to a network user, send to user INTERNET
on system SMTPRTE, using the INETADR field for the recipient –
‘someone@somewhere.com’

Thanks to 'unknown'
Back

Guide to block mail relay and spamming

You can block mail relay and spamming using the following instructions.
The information was copied from here:

Special instructions for controlling RELAY and CONNECTIONS
----------------------------------------------------------
If you do not choose to take advantage of this enhanced function,
nothing needs to be done. If you want to take advantage of this
enhanced function, you should do the following:

Special Instructions for RESTRICTING RELAY:
1. Create a Source Physical File QUSRSYS/QTMSADRLST
record length 92 (12 characters for line count and change
information). The file must be ccsid 500.

CRTSRCPF FILE(QUSRSYS/QTMSADRLST) CCSID(500)

2. Create a Source Physical File member ACCEPTRLY
To create a member for a file that already exists (and go into
edit):

STRSEU SRCFILE(QUSRSYS/QTMSADRLST) SRCMBR(ACCEPTRLY)

3. Add a record with the dotted decimal address of the ALLOWED user.
Only addresses in the list will be allowed to relay.
Put one address and mask per line, (a mask is optional).

An example entry would be:
1.2.3.4 255.255.0.0
In this example the mask and the address would be combined (AND)
to allow all addresses starting with '1.2' e.g. '1.2.5.6'

Another example:
7.8.9.3 255.255.255.255
This would allow only one address, 7.8.9.3. It is the same as
7.8.9.3

Special Instructions for RESTRICTING CONNECTIONS:
1. Create a Source Physical File QUSRSYS/QTMSADRLST record
length 92 (12 characters for line count and change information).
The file must be ccsid 500.

CRTSRCPF FILE(QUSRSYS/QTMSADRLST) CCSID(500)

2. Create a Source Physical File member REJECTCNN
To create a member for a file that already exists (and go into
edit):

STRSEU SRCFILE(QUSRSYS/QTMSADRLST) SRCMBR(REJECTCNN)

3. Add a record with the dotted decimal address of the REJECTED user.
This blocks relay and mail delivery from this address.
Put one address and mask per line (a mask is optional).

An example entry would be:
1.2.3.4 255.255.0.0
In this example the mask and the address would be combined (AND)
to reject all address starting with '1.2' e.g. '1.2.5.6'

Another example:
7.8.9.3 255.255.255.255
This would reject only one address, 7.8.9.3. It is the same as
7.8.9.3

Instructions for activating relay and connection lists
1. End the smtp server
ENDTCPSVR SERVER(*SMTP)

2. If data area for blocking all relays exists, delete it.

To see if the data area exists:
DSPDTAARA DTAARA(QUSRSYS/QTMSNORLY)

To delete the data area:
DLTDTAARA DTAARA(QUSRSYS/QTMSNORLY)

3. Start the smtp server
STRTCPSVR SERVER(*SMTP)

Special notes:
1. If the data area for blocking relays is used (QUSRSYS/QTMSNORLY),
ALL relays will be blocked. If the data area is not there, but
QUSRSYS/QTMSADRLST.ACCEPTRLY exists and has at least one entry,
then only addresses in the list will be allowed to relay.

2. If the address is in QUSRSYS/QTMSADRLST.REJECTCNN it will not
be allowed to connect. This blocks relay and mail delivery from
this address. If QUSRSYS/QTMSADRLST.REJECTCNN does not exist
or has no valid entries, then all connections will be allowed.

3. If journaling is on, rejected addresses will be journaled.
To find out if journaling is on:
Use PF4 on command CHGSMTPA, look for parameter Journal
which would be *YES for on.

To display journal,('sues/jrnl' is your directory and file,
'dec14' is the name of the member you are creating):

DSPJRN JRN(QZMF) OUTPUT(*OUTFILE) OUTFILE(sues/jrnl) OUTMBR(dec14)
ENTDTALEN(512)

DSPPFM FILE(sues/jrnl) MBR(dec14)

Rejected connections will have the entry, starting in column 195:
"9S CONNECTION REFUSED 1.2.3.4"
(1.2.3.4 is the dotted decimal address rejected.)

Rejected relays will have the entry, starting in column 195:
"9V RELAY REFUSED 1.2.3.4"
(1.2.3.4 is the dotted decimal address rejected.)

These journal entries will have a message id of 0.

4. Relays will be rejected with the SMTP protocol response,
in the SMTP client to SMTP server conversation:
"553 Relaying blocked at this site."

5. Connections will be rejected with the SMTP protocol response,
in the SMTP client to SMTP server conversation:
"421 Service not available, access denied."

6. Only the first 10,000 entries in each table will be read.
Lines beginning with '*' will be treated as comments.
The file must be ccsid 500. Only put one address and mask per
line. If you FTP your file between systems, make sure it is
created as a source physical file on the receiving system first.

7. Error messages will appear in the QTSMTPSRVR joblog as follows:
Entries in the QUSRSYS/QTMSADRLST that are not valid:
"TCP9508" "Internet address not valid."

Note that the above message will always be followed by the
following message to indicate which file member has problems.
The entries not in error will still be used.

Any error with file QUSRSYS/QTMSADRLST:
"TCP2062" "SMTP job not able to use file QTMSADRLST."
Except for entry errors, the above message will result in the
actions that would occur if there were no file.

Error getting temporary space for lists, which will result in
actions that would occur if there were no file:
"TCP1062" "Not enough storage available."

Read errors on file QUSRSYS/QTMSADRLST, may result in a partial
file being used:
"TCP12B5" "Unable to read data from file QTMSADRLST."

8. If changes are made to QUSRSYS/QTMSADRLST, the SMTP Server
must be restarted for the changes to take effect:
ENDTCPSVR SERVER(*SMTP)
STRTCPSVR SERVER(*SMTP)

Last news:

However, a new test using a customer's domain name and the %sign is being done.
The % sign is a routing character and can be disabled. SMTP users should run the
CHGSMTPA command, and press F4 to prompt. Then, change the Percent Routing Character
parameter value to *NO if you do not use this option. This disables this option,
and you will not be labeled as an OPEN RELAY by the testing companies.

Thanks to Makins

From V5R1 on Do the following: chgsmtpa alwrly(*list) addsmtple type(*accept) intnetadr('n.n.n.n') subnetmask('255.255.255.255') - where n.n.n.n is the IP address of your AS/400 endtcpsvr *smtp strtcpsvr *smtp Note. If you are coming from V4 and you already used the anti-spam technique, just enter command chgsmtpa alwrly(*list) and restart SMTP. File QUSRSYS/QTMSADRLST is read from SMTP, its records converted to "smtp-list-entries", and the file is deleted. This is why, if you try to enter the above command addsmtple type(*accept) intnetadr('n.n.n.n') you get the message "duplicate entry". Thanks to Giovanni Perotti

SMTP - High usage, but not running To prevent this from happening in the first place, you'll need to restrict SMTP relaying. Use the CHGSMTPA command to change the allow relay setting from *ALL to *LIST. Then, create an accept entry for your AS400 using the ADDSMTPLE command. For example, if the IP address of your system is 10.1.1.1, then run the command: ADDSMTPLE TYPE(*ACCEPT) INTNETADR('10.1.1.1') SUBNETMASK('255.255.255.255') This will allow your AS400 to send mail, but nobody else. Once this has been set up, end the SMTP server and restart it. If high activity continues, then end the SMTP server again and run the following command: CRTDTAARA DTAARA(QUSRSYS/QTMSCLEAN) TYPE(*CHAR) LEN(1) VALUE('C') AUT(*ALL) Then, start your SMTP server one more time. At this point, any pending messages that the server was continuing to try and send will be cancelled. Thanks to Rich Loeber

Back

Problems with MSF and SMTP server

Q:
It seems that every time MSF starts up, we get errors logged about undeliverable messages.
I suspect that these are really old messages that should just be cleared out, but I'd like
to confirm that.

Google is not my friend today, nor any of the other days that I've tried to look at this
problem.  I've read over what seem to be the relevant portions of the 'AnyMail/400 Mail
Server Framework Support' book from IBM (SC41-5411-00).

The only option I've turned up in my searches is to use the QzmfRtvMailMsg API to get
information about the undeliverable messages. This is rather a complex API.  Is there another
way to peek at MSF messages?

A: I've run into this problem on occasion when a bad email address causes it to have a problem. The solution is to clear things out completely in the queue and restart fresh. I have the following CL program that I use. It has one parm. If the parm is set to C, only the most recent entry in the queue is cleared. If it is set to X, then the entire queue is cleared. End MSF before you run this, then restart it when this is done: PGM PARM(&CODE) DCL VAR(&CODE) TYPE(*CHAR) LEN(1) /* 'X' or 'C' */ IF COND((&CODE *EQ 'X') *OR (&CODE *EQ 'C')) + THEN(DO) ENDTCPSVR SERVER(*SMTP) CRTDTAARA DTAARA(QUSRSYS/QTMSCLEAN) TYPE(*CHAR) LEN(1) + VALUE(&CODE) AUT(*ALL) DLYJOB DLY(10) STRTCPSVR SERVER(*SMTP) ENDDO ENDPGM Thanks to Rich Loeber
A: Actually, the MSF mail messages do not live in the IFS, they are a hold over from the old Office Vision/400 and/or Document Library Object days. But if you poke around enough you can find them. Here's some snippets and tips which should get you headed in the right direction. As always, backup is your friend. You can do a SAVDLO DLO(*MAIL) DEV(*SAVF) to grab a copy of things before you start deleting them. They were based on the IBM document below which is no longer available online, or at least I wasn't able to find it anymore. __________________________________________________________________ IBM Software Technical Document __________________________________________________________________ Document Number: 15451115 Functional Area: AS/400 Mail Subfunctional Area: OfficeVision - Mail Sub-Subfunctional Area: General OS/400 Release: ALL Product: Operating System/400 - OS/400 COMM BASE (5716SS1CM) Product Release: N/A __________________________________________________________________ Document Title Example (CL) - Cleanup SNADS Mail Distributions __________________________________________________________________ ===== Start by using DSPDIRE to outfile to get a list of all users who could be sending MSF mail ===== DSPDIRE OUTPUT(*OUTFILE) OUTFILE(&OUTLIB/&OUTFILE) OUTMBR(*FIRST *REPLACE) DETAIL(*FULL) + OUTFILFMT(*TYPE3) OUTDTA(*ALL) ===== Even if you use an *ALLOBJ userid, or even QSECOFR, you may not be able to access the mail for the users listed above. I ended up having to loop through the above file and use the Grant User Permission (GRTUSRPMN) command to myself. ===== DCLF FILE(QSYS/QAOSDIRX) RCDFMT(OSDIRX) OVRDBF FILE(QAOSDIRX) TOFILE(&OUTLIB/&OUTFILE) LOOP: RCVF RCDFMT(OSDIRX) MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE)) IF COND(&DXUSRP *NE ' ') THEN(+ DO) GRTUSRPMN TOUSER(BDOLINAR) FORUSER(&DXUSRP) ENDDO DONE: ENDPGM ===== And now that you have the ability to look at all the MSF mail, there are two "piles" of mail, incoming and outgoing. Use the Query Distribution (QRYDST) command to send each of them to their own outfile by looping through the DSPDIRE list of users. From what I remember, the *IN and *OUT files have different layouts and use different parameters on the QRYDST command. ===== LOOP: RCVF RCDFMT(OSDIRX) MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE)) IF COND(&DXUSRP *NE ' ') THEN(DO) QRYDST OPTION(*IN) USRID(&DXDEN &DXDGN) + OUTFILE(&OUTLIB/QRYDSTIN) OUTMBR(*FIRST + *ADD) STATUS(*ALL) MONMSG MSGID(CPF0000) ENDDO GOTO CMDLBL(LOOP) DONE: ENDPGM ===== LOOP: RCVF RCDFMT(OSDIRX) MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE)) IF COND(&DXUSRP *NE ' ') THEN(+ DO) QRYDST OPTION(*OUT) USRID(&DXDEN &DXDGN) + OUTFILE(&OUTLIB/QRYDSTOUT) OUTMBR(*FIRST + *ADD) MONMSG MSGID(CPF0000) ENDDO GOTO LOOP DONE: ENDPGM ===== The "Distribution ID" fields, LINDID and OUTDID, along with the Recipient/Sender Identifier, LINRUI and OUTSUI, and Recipient/Sender Address LINRUA and OUTSUA fields, is the information needed from the two QRYDST files to actually identify an MSF mail message so you can send them to their own output files. While in theory you should be able to send both incoming and outgoing distributions to an output file, I was only able to do it for the incoming. All the outgoing ones were *ERR type messages, so I just deleted them to get them out of the system. ===== DCLF FILE(QSYS/QAOSILIN) RCDFMT(OSLIN) /* GLOBAL MONMSG */ MONMSG MSGID(CPF9098 CPF900C) OVRDBF FILE(QAOSILIN) TOFILE(BCDLIBOV4/QRYDSTIN) LOOP: RCVF RCDFMT(OSLIN) MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE)) RCVDST DSTID(&LINDID) USRID(&LINRUI &LINRUA) + OUTFILE(&OUTLIB/RCVDSTIN) ACKRCV(*YES) + DSTIDEXN(*NONE) KEEP(*YES) GOTO CMDLBL(LOOP) DONE: ENDPGM ===== ===== /* WARNING - THIS CODE DELETES THE OUTGOING DISTRIBUTIONS WITHOUT SAVING THEM */ DCLF FILE(QSYS/QAOSILOT) RCDFMT(OSLOUT) OVRDBF FILE(QAOSILOT) TOFILE(BCDLIBOV4/QRYDSTOUT) LOOP: RCVF RCDFMT(OSLOUT) MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE)) DLTDST DSTID(&OUTDID) OPTION(*OUT) USRID(&OUTSUI + &OUTSUA) DSTIDEXN(&OUTDEX) MONMSG MSGID(CPD9022 CPF900A) EXEC(DO) /* CPD9022 DISTRIBUTION ISSPRD-BSYPGMR-0039-00 CANNOT BE F DLTDST DSTID(&OUTDID) OPTION(*ERR) USRID(&OUTSUI + &OUTSUA) DSTIDEXN(&OUTDEX) MONMSG MSGID(CPF900A) ENDDO ===== Thanks to Brian Dolinar
Back

System Reference Codes at IPL time

IPL SRC Format Description:
As the system performs an IPL, SRCs appear on the control panel. The SRCs
indicate the status of the IPL and are often useful in problem analysis.
The following list provides information on the IPL process and shows some
SRCs (in numeric order) that can appear during an IPL.

IPL   SRC    Function Performed
C1xx 1xxx  Service Processor ROS IPL in progress
C1xx 1006  Service processor ROS loading RAM from MFIOP directed device
C1xx 1007  Service Processor ROS retrying attempt to load RAM from MFIOP directed device
C1xx 1008  Service Processor ROS attempting to load RAM from non-MFIOP directed device
C1xx 1009  Service Processor ROS retrying attempt to load RAM from non-MFIOP directed
                device
C1xx 1016  Service Processor ROS attempting to load RAM from MFIOP default device
C1xx 1018  Service Processor ROS attempting to load RAM from non-MFIOP default device
C1xx 1019  Service Processor ROS retrying attempt to load RAM from non-MFIOP default device
C1xx 100C  Service processor ROS IPL complete, branch to RAM Loader
C1xx 1030  Loading Service Processor system LIC from MFIOP device
C1xx 1050  Loading Service Processor system LIC from non-MFIOP device
C1xx 2001  Service Processor is setting up to test and load system processor
C1xx 2002  Service processor is testing system processor and main storage
C1xx 2003  Service processor is loading system processor
C100 2034  LIC (system) has been initialized; control has passed to system processor
C1xx 2050  Service Processor is waiting for Load Source device
C1xx 2060  Service Processor started a read command from Load Source
C1xx 2090  Service Processor completed a read command from load source
C1xx 80xx  Service Processor/Control Panel communication in progress
C1xx 805x  Service Processor is loading Control Panel LIC
C1xx B1xx  Basic assurance test completed on MFIOP
C100 D009  LIC (system) running initialization
C200 1xxx  Secondary Partition Early IPL Initialization Phase
C200 1100  Adding partition resources to the secondary configuration
C200 11FF  Partition resources added successfully
C200 2xxx  Secondary Partition SPCN Tower Power ON Phase
C200 1200  Checking if IPL is allowed
C200 12FF  Partition IPL is allowed to proceed
C200 1300  Initializing ISL roadmap
C200 13FF  ISL roadmap initialized successfully
C200 1400  Initializing SP Communication Area #1
C200 1410  Initializing IPL parms
C200 14FF  IPL parms initialized successfully
C200 3xxx  Secondary Partition Bus Unit ISL Phase
C200 3100  Validating ISL command parameters
C200 3111  Waiting for Bus object to become operational
C200 3112  Waiting for bus unit to become disabled
C200 3150  Sending ISL command to bus unit
C200 31FF  Waiting for ISL command completion
C200 32FF  ISL Command complete successfully
C200 4xxx  Secondary Partition Load Source Device Connection Phase
C200 4100  Waiting for load source device to enlist
C200 4200  Load source device has enlisted
C200 4300  Preparing connection to load source device
C200 43FF  Load source device is connected
C200 5xxx  Secondary Partition Main Storage Dump Phase
C200 5100  Preparing to initiate MSD phase
C200 5110  Loading SID 82 from load source device
C200 5115  MSD Phase I
C200 5120  Writing processor registers into SID 82
C200 5125  MSD Phase II
C200 5130  Writing mainstore pages to the load source device
C200 5135  MSD Phase III
C200 5140  Storing (final) SID 82 back to the load source device
C200 5150  Allocating the hardware page table
C200 51FF  MSD processing complete
C200 6xxx  Secondary Partition Load LIC from Load Source Phase
C200 6000  Locating First LID information on the loadsource
C200 6010  Locating Next LID information on the loadsource
C200 6020  Verifying LID information
C200 6030  Priming LP Configuration LID
C200 6040  Preparing to initiate LID load from loadsource
C200 6050  LP Configuration LID primed successfully
C200 6060  Waiting for LID load to complete
C200 6100  LID load completed successfully
C200 7xxx  Secondary Partition Load Source Device Disconnection Phase
C200 7100  Disconnecting from load source device
C200 7110  Preparing to remove the load source IOP from the primary partition
C200 7120  Load source IOP has been successfully removed from the primary partition
C200 71FF  Load source is successfully disconnected
C200 8xxx  Secondary Partition Start Processors Phase
C200 8100  Initializing SP Communication Area #2
C200 8104  Loading data structures into mainstore
C200 8110  Initializing event paths
C200 8120  Starting processors
C200 81FF  Processors started successfully, now waiting to receive the continue
                acknowledgment from SLIC
C200 8200  Continue acknowledgment received from SLIC
C200 82FF  VSP IPL complete successfully
C3xx xxxx  System Processor or Main Storage Diagnostic in progress
C5xx xxxx  LIC system hardware initialization
C500 C92B  Waiting for console device - error condition only if console not found
C6xx 1800  LIC SPCN setup
C600 3900  SP transfer control of Bus 1 (BCU Switch) to LIC is Complete and LIC Machine
                Facilities component is initialized
C600 3910  LIC has initiated PCI Bus Reset to all Bus 1 devices except the SP
C600 3911  LIC has initiated self-test of all Bus 1 devices except the SP
C600 3912  LIC is initiating IPL of the Load Source IOP
C600 3913  LIC is initializing the Load Source IOP messaging functions
C600 3914  LIC has detected a Load Source IOP problem and is resetting the IOP, or the IOP
                has requested a reset after an internal Flash memory LIC update
C600 3915  LIC has initiated the Load Source IOP self-load
C600 3916  During self-load, the Load Source IOP signaled LIC that it is initiating an internal
                Flash Memory update or other critical function
C600 3917  The Load Source IOP has completed IPL of its operational load, LIC is waiting for
                the IOP to report its attached IO resources
C600 4001  Static paging
C600 4002  Start limited paging, call LID manager
C600 4003  Initialize IPL/Termination (IT) data area / set up node address communication area
                (NACA) pointer
C600 4004  Check and update MSD SID
C600 4005  Initialize event management is running
C600 4006  IPL all buses
C600 4007  Start error log ID
C600 4008  Initialize I/O service
C600 4009  Initialize I/O machine
C600 4010  Initialize IDE (interactive device exerciser)
C600 4011  Initialize remote services
C600 4012  Initialize RMAC component data values
C600 4013  Initialize context management
C600 4014  Initialize RM (component) seize lock
C600 4015  Initialize MISR
C600 4016  Set time of day
C600 4017  Initialize RM (component) process management
C600 4018  Initialize error log
C600 4019  Restart the service processor
C600 4020  Initialize machine services
C600 4021  Initialize performance data collector
C600 4022  Initialize event management
C600 4023  Create MI boundary manager tasks
C600 4024  Disable CPM
C600 4025  Initializes battery test
C600 4026  Hardware card checkout
C600 4027  Start integrated device exerciser (Type C IPL only)
C600 4028  Start DST
C600 4029  Make IPL task not critical
C600 4030  Free static storage
C600 4031  Destroy IPL task
C600 4032  Initialize Integrated File System descriptor management
C600 4033  Initialize LPAR Virtual I/O
C600 4050  Storage management recovery is running
C600 4051  Start LOG is running
C600 4052  Trace table initialization is running
C600 4053  Context rebuild is running
C600 4054  Start product activity log and Advanced Peer-to-Peer Networking (APPN) is running
C600 4055  Authority recovery is running
C600 4056  Journal recovery is running
C600 4057  Database recovery is running
C600 4058  Journal synchronization is running
C600 4059  Commit recovery is running
C600 4060  Database initialization is running
C600 4061  Journal IPL clean up is running
C600 4062  Commit initialization is running
C600 4064  System Object Model ? (SOM) recovery is running
C600 4065  Start operating system is running
C600 4100  Searching for Load Source Candidate (D-mode only)
C600 4101  Opening media-file to install LIC service displays with proper National Language
                Version
C600 4102  Loading and linking from media-file to install LIC service displays with proper
                National Language Version
C600 4201  Storage management recovery
C600 4204  Synchronization of mirrored MSD
C6xx 4205  Synchronization of mirrored data (where xx is percent complete).
C600 4240  Reclaim main storage
C600 4250  Storage management subset directory recovery
C600 4255  Defragmentation utility
C600 4260  Storage management directory recovery
C600 4272  ASP overflow recovery
C600 4300  Static paging is available for the link loader
C600 4301  Applying temporary PTFs. If the IPL stops at this point, you might need to install
                the Licensed Internal Code again.
C600 4302  Applying modules. If the IPL stops at this point, you might need to install the
                Licensed Internal Code might again.
C600 4303  Temporarily applied PTFs have reached the static paging phase
C600 432A  Resolving references to run Mode A; you can safely stop the system while it is doing
                this work.
C600 432B  Resolving references to run Mode B. You may safely stop the system while it is doing
                this work.
C600 4330  Full paging is available; workstation HRI processing
C600 4331  Freeing unused nucleus pages
C600 4332  Permanently applying PTFs. If the IPL stops at this point, you may need to install
                the Licensed Internal Code again.

Main Storage Dump IPL SRCs:
C6xx 4400  Unattended DASD checker started
C6xx 4401  Attended DASD checker started
C600 4402  Main storage dump manager and storage management recovery started
C600 4403  Storage management recovery ended
C6xx 4404  LIC log started (where xx is dump copy percent completed).
C600 4405  Dump auto copy completed. Shutdown or programmed IPL has started.
C600 4406  Shutdown or programmed IPL (MSD related) has started.

Continuously Powered Main Storage (CPM) IPL SRCs:
C6xx 4410  Unattended DASD checker started
C6xx 4411  Attended DASD checker started
C600 4412  Storage management recovery started
C600 4414  LIC log started
C600 4416  Shutdown or programmed (CPM-related) IPL has started

Dedicated Service Tools (DST) SRCs for Attended IPLs:
C600 4500  Verifying network attributes
C600 4501  Looking for the console
C600 4502  Starting DST display task
C600 4503  Checking possible machine-readable information (MRI) on media
C600 4504  Verifying system serial number
C600 4505  Verifying system type
C600 4506  Verifying system-unique ID (OS/400 only
C600 4507  Starting 'before DST' DASD checker
C600 4508  Verifying system password (if DASD check OK; if not OK this is checked when
                IPLing past DST)
C600 4509  Starting DASD migration function (only if migrating)
C600 450A  Starting 'after DST' DASD checker (can happen twice)
C600 450C  DST IPL status

Dedicated Service Tools (DST) SRCs for Unattended IPLs:
C600 4500  Verifying network attributes
C600 4504  Verifying system serial number
C600 4505  Verifying system type
C600 4506  Verifying system-unique ID (OS/400 only)
C600 4508  Verifying system password (if DASD check OK)
C600 450A  Starting 'after DST' DASD checker

Licensed Internal Code (LIC) SRCs for IPLs:
C600 4A57  Parallel database recovery is at Pass 1
C600 4A60  Parallel database initialization is at Pass 1
C600 4B57  Parallel database recovery is at Pass 2
C600 4B60  Parallel database initialization is at Pass 2
C600 4C57  Parallel database recovery is at Pass 3
C600 4C60  Parallel database initialization is at Pass 3
C600 4F57  The system is recovering all database objects. This step can take several hours.
                It can occur any time when database recovery is in progress.
C600 4F60  The system is examining all objects during database initialization. It can occur
                any time when database initialization is in progress.

Note:   At this point LIC initialization is complete, and operating system starts.
          All hardware is verified.


OS/400 (only) operating system SRCs:
C900 2810  Reclaim machine context
C900 2820  Resolve system objects
C900 2825  Convert Work Control Block Table
C900 2830  System value object
C900 28C0  Prepare SPCF job
C900 28C5  Initialize system objects
C900 2910  Start system logging
C900 2920  Library and object information repository (OIR) cleanup
C900 2925  Verify POSIX** root directories
C900 2930  Database cross-reference
C900 2940  Console configuration
C900 2950  Install complex objects
C900 2960  Sign on processing
C900 2965  Software Management Services (SMS) initialization
C900 2967  Applying PTFs
C900 2968  IPL options
C900 2970  Database recovery part 1, journal recovery part 1
C900 2973  This recovery step attempts to perform any needed recovery for Database files
                that were definitionally being changed, created or deleted when an abnormal
                system end occurred.
C900 2976  This recovery step verifies the object recovery list performs any needed recovery
                for Journals and Journal Receivers.
C900 2978  This IPL Status SRC is displayed when SRCs C900 2A70 - C900 2976 have been
                completed
C900 2980  Storage requirements
C900 2990  Performance adjustments
C900 29A0  System control block
C900 29B0  Spool initialization
C900 29C0  Work control block table
C900 2A80  Before starting system jobs
C900 2A85  Bringing up POSIX SAG
C900 2A87  POSIX SAG restart and signals initialization
C900 2A90  Starting system jobs
C900 2A95  Abnormal Work Control Block Table cleanup
C900 2AA0  Damage Notification
C900 2AA1  This recovery step either rolls back or completes certain uncompleted Database
                 operations that were run under Commitment Control
C900 2AA2  This recovery completes certain Journal operations that were in
                 progress when the system ended processing
C900 2AA3  This recovery sends messages to QHST for Database files that may
                 have been damaged by a system end
C900 2AA3  This recovery sends messages to QHST for Database files that may
                 have been damaged by a system end
C900 2AA4  This IPL Status SRC is displayed when SRCs C900 2AA0 - C900 2AA3
                 have been completed
C900 2AA5  Integrated File System/New File System (NFS) directory recovery
C900 2AB0  Database Recovery part 2
C900 2AC0  Document Library Object (DLO) recovery
C900 2B10  Establish event monitors
C900 2B30  QLUS job
C900 2B40  Device configuration
C900 2C10  After system arbiter
C900 2C20  SNADS recovery
C900 2C25  ZMF component (Mail Enablement (OeDS) Framework) recovery
C900 2C40  Work Control Block Table cleanup
C900 2F00  IPL Complete

Operating system initialization is complete when the sign-on screen
displays on the console.

Thanks to Al Barsa, Jr.
Back

Backing up the IFS system

	SAV        DEV('/tap01') OBJ(('/*') ('/qsys.lib' *OMIT) +
	             ('/qdls' *OMIT) ('/qibm/proddata' *OMIT) +
	             ('/qca400' *OMIT) ('/qisafix' *OMIT) +
	             ('/qopensys/qibm/proddata' *OMIT)) +
	             OUTPUT(*print) UPDHST(*YES)


The gotcha is with the device.  /tap01 is a symbolic link without which
you'd have to type '/qsys.lib/tap01.devd'.

Thanks to Phil
Back

Null-terminated data - to do or not to do

However, since the inet_addr function is designed for C programs,
the string SHOULD be null-terminated.  (correct me if I'm wrong!)
Therefore, my code would look like this:

D inet_addr       PR            10I 0 ExtProc('inet_addr')
D   DottedAddr                  16A

C                   eval      TcpAddr = %trim(TcpAddr) + x'00'
C                   eval      sin_addr = inet_addr(tcpAddr)

This is right, but can be coded more easily if you are on V3R7 or greater
using a little-known parameter feature: OPTIONS(*STRING).

D inet_addr       PR            10I 0 ExtProc('inet_addr')
D   DottedAddr                    *   VALUE OPTIONS(*STRING)

C                   eval      sin_addr = inet_addr(%trim(tcpAddr))

Most "char *" parameters in C functions require null-terminated data.  You can
handle the null-termination yourself, or you can ask the RPG compiler to do it
for you using OPTIONS(*STRING) on a pointer parameter passed by value.  With
this type of parameter you can either pass a pointer or a character string.  If
a character string, the compiler creates a temporary with your value followed by
a null character, and passes that instead.

Left as an exercise for the keen student: check out the %STR bif to see how to
handle pointers to null-terminated data within your RPG program without scanning
for x'00' and substringing.

Thanks to Barbara Morris
Back

The reason for semicolon on /Free specs?

Q:	Why didn't you listen to the many lone voices about that stupid semicolon on /Free
specs?

A:	You really want me to dredge up the logic behind that decision yet again? OK, here
goes. But first some history:

Back prior to V5R1, we had an enhancement survey where one of the items to be voted on
was free-form calcs. We thought at the time that it would be a big effort, and so a price tag
of $100 was given to it. In spite of the high cost, some voters actually blew their whole wad
on it! After thinking a bit more about the issue, I realised that the cost didn't have to be
that much, especially considering we really didn't have to worry anymore about some things
like conditioning indicators, resulting indicators, and definition of variables on the calc spec.
By V4R4, these things were generally considered to be bad style anyways. The issue of
multi-part factors remained, but that issue disappeared when we decided that the opcodes
that used multi-part factors could be replaced by existing or new built-in functions.

At this point, the implementation became much easier. The arguments are parsed and the
information about them placed into the same variables used to process them as if they were
coded on fixed-form calcs.

The biggest work in fact was implementing a new set of built-in functions to handle certain
opcodes that would not be allowed in free-form calcs, like LOOKUP, SCAN, and XLATE, as well
as the date/time/timestamp operations. But voters indicated that they wanted many of these
anyways, even without free-form calcs.

Then came the matter of syntax. At first, the design indicated free-form calcs with characters
CX in positions 6-7, with C+ indicating continuation. Later the CX was changed to CF, but the
principle remained the same. This was almost universally panned by RPG programmers, and
eventually we decided this had to be changed. At that time, I was working on another project,
but I was dragged back early to the RPG development team to change the design before it was
too late for V5R1.

So first, we decided that positions 6-7 of the free-form calc spec had to be blank. Then we had
to deal with the issue of continued statements.

But before we go further, here's one general principle in RPG language design: Since RPG runs
primarily on EBCDIC machines, and since various EBCDIC codepages have different code points
for different characters, the RPG character set is necessarily limited. That is, if for instance
we decided on the backslash as having a particular meaning in RPG, there may well be problems
in moving to different code pages.

So the question is: continuation character or end of statement delimiter?

The RPG language already had umpteen different styles of continuation. For instance, character
literals had two different styles of continuation, and numeric literals had their own different
style. We didn't want to use either '+' or '-' to indicate continuation since those would be too
easily confused with character literal continuation, as well as the binary addition, subtraction,
and catenation operators. Beyond those, there weren't really many good alternatives in the
available invariant EBCDIC characters. And besides, we didn't really want to introduce yet
another style of continuation.

Consider how continuation was already expressed in the Keywords entries and the
Extended-Factor-2 entry. No special continuation character was needed in those entries, since
continuation was indicated by blanks at the beginning of the continued spec. We felt that the
precedent of no continuation character was reasonable for the free-form calcs as well. Then,
the decision to select the semi-colon as the end of statement delimiter was a no-brainer, since
it was already very commonly used for that purpose.

The next decision was this: Was the end of statement delimiter needed for all calcs? That is,
in some languages, a semi-colon is not needed after some keywords, like ELSE. We decided
that the simplest rule to remember was that the semi-colon was needed after each and every
opcode. Thus, no exceptions to remember.

In conclusion, we certainly do appreciate that not everyone likes the choice of statement
delimiter style of free-form coding. (Practically all new things in the language have both fans
and detracters!) But hopefully, you now have some understanding of the process that led to
this particular design decision.

Thanks to Hans Boldt
Back

Debug doesn't show values for all fields in a file

Q: I'm trying to use the debugger and can't see all of my fields! Let me
explain. I have an RPG IV program that reads physical file Customer and prints a
report based on certain criteria. The validity of some of the information on the
report came into question, so I decided to use the debugger to see what was
causing the problem.

I placed a breakpoint in my program immediately after reading a record from file
Customer. I then tried to display the value of the fields the file contains.
Several of the fields were blank. Puzzled, I used DspPFM (Display Physical File
Member) to display the file. I was even more puzzled after this showed that the
record did indeed contain data in the fields reported as blank by the debugger!

Is there a problem with the debugger? Do I need PTFs?

A: There's not a problem with the debugger. The debugger isn't showing you a
value for the fields in question because the information isn't available to the
debugger. You see, the RPG compiler optimizes unreferenced fields out of your
program. In other words, any field in file Customer that is not specifically
referenced by an RPG operation isn't included in the program.

If you really want the unreferenced fields available to the debugger, you can
include an H-spec with keyword Debug. This prevents the compiler from optimizing
out unreferenced fields.

Thanks to Club Tech iSeries Newsletter
Back

Converting Num. Variables to Zero-Filled Char. in Free-Format

Q: With fixed-format RPG, I've always used the Move op-code to move a
numeric variable to a character variable. The resulting character variable
is right-justified and zero-filled. Now I'm beginning to work with free-
format RPG, and the Move op-code is not supported. I tried using the %Char
built-in function (BIF), but the result is not zero-filled and is left-
justified. What can I do in free-format RPG to convert a numeric variable to
a right-justified, zero-filled character variable?

A: There are several techniques you can use in free-format RPG to convert a
numeric variable to a right-justified, zero-filled character variable. I'll
show you several options.

In each of the following examples, Nbr is a nine-digit numeric variable with
a value of 12345, and the resulting character variable (Chr) is a nine-byte
character variable with a value of 000012345.

The following examples use the %Char BIF to convert Nbr to character. They
trim this intermediate character result and concatenate it to the end of a
string of character 0s.

This example explicitly concatenates a constant of nine 0s (the length of
the character field is 9).

	D Nbr S 9P 0 Inz( 12345 )
	D Chr S 9

	/Free
	EvalR Chr = '000000000' + %Trim( %Char( Nbr ) ) ;
	/End-Free

The following example defines a nine-byte variable (Zero9) and initializes
it to all character 0s. This variable is then used in the concatenation
rather than using a constant as in the previous example.

	D Nbr S 9P 0 Inz( 12345 )
	D Chr S 9
	D Zero9 S 9 Inz( *All'0' )

	/Free
	EvalR Chr = Zero9 + %Trim( %Char( Nbr ) ) ;
	/End-Free

The next example defines a 30-byte variable (AllZero) and initializes it to
all character 0s. As in the previous example, this variable is used in the
concatenation. This is an improvement over the previous technique because 30
bytes is the maximum length for a numeric field. This single field,
therefore, can be used for any conversion.

	D Nbr S 9P 0 Inz( 12345 )
	D Chr S 9
	D AllZero S 30 Inz( *All'0' )

	/Free
	EvalR Chr = AllZero + %Trim( %Char( Nbr ) ) ;
	/End-Free

The following example uses another BIF, %EditC (Edit code), to perform the
desired conversion. I suggest this technique.

	D Nbr S 9P 0 Inz( 12345 )
	D Chr S 9

	/Free
	Chr = %EditC( Nbr : 'X' ) ;
	/End-Free

Finally, you can use a data structure to perform the conversion. Simply
define a zoned numeric subfield in your data structure and set its value to
that of the number you wish to convert.

D Nbr S 9P 0 Inz( 12345 )

D Chr DS
D Nbr2 9S 0

/Free
Nbr2 = Nbr ;
/End-Free

Thanks to Club Tech iSeries Newsletter
Back

Service Programs -- The Chicken and The Egg

Q: I'm trying to create a few service programs and have a situation that I'm not sure
I can get out of! Here's the story...

I have service programs SrvPgm1, SrvPgm2, and SrvPgm3. SrvPgm1 references procedures
in SrvPgm2 and SrvPgm3. Normally, I would create service programs SrvPgm2 and SrvPgm3
to make their procedures available and then create service program SrvPgm1. However,
in this case, SrvPgm2 and SrvPgm3 also reference procedures in SrvPgm1. Therein lies
the chicken and the  egg. To create SrvPgm1, I must first create SrvPgm2 and SrvPgm3;
to create SrvPgm2 and SrvPgm3, I must first create SrvPgm1!

If somebody knows which came first, the chicken or the egg, please let me know so that
I can get these service programs compiled!

A: Here's how you can solve your problem.

	1. Create SrvPgm1 with Option(*UnrslvRef).
	2. Create SrvPgm2 and SrvPgm3.
	3. Recreate SrvPgm1 and do not specify Option(*UnrslvRef).

Thanks to Barbara Morris
Back

Finding Commands and Menus

Despite the logical naming convention for OS/400 commands and menus,
it's easy to get stymied while groping for the proper name.

Here are a few ways to easily find the command you want:
(1) Enter SLTCMD *ALL to display a list of all commands.

(2) Enter GO CMDxxxx, where xxxx is one or more characters, including
a generic name, which will show all command menus starting with
CMDxxxx. For example, CMDFI* will show two command menus, CMDFIL and
CMDFILE.

(3) Enter SLTCMD and a generic name to display a list of all commands
that contain one or more characters. For example, SLTCMD dspf* will
display all commands starting with DSPF, such as DSPF, DSPFAX, DSPFD,
and so on.

(4) Enter a generic name on the OS/400 command line (SLTCMD is assumed
by the system). For example, entering dspf* on the command line is
identical to entering SLTCMD dspf*.

It's preferable to use method 3 and not 4, since the "S" in SLTCMD is
close to the "D" on the keyboard, and with the right authority, you
could inadvertently perform a DLTCMD (Delete Command) operation. That
would be a bad thing.

Here are two easy ways to locate a menu:
(1) Enter GO *ALL, which displays all menus.

(2) Enter GO and a generic name to display a list of all menus that
contain one or more characters. For example, GO file* will display all
menus starting with FILE, such as FILE, FILESYS, FILETFR, and so on.

Notes to the text above:
I meant to say it was preferable to enter a generic name on the OS/400
command line instead of SLTCMD and a generic name.
(I had it backwards in the last issue.) For example, enter "dspf*"
instead of "SLTCMD dspf*". Omitting SLTCMD reduces the chances of
entering DLTCMD, which deletes commands -- a bad thing.

Also, Sandeep G. Bhat pointed out that to list all the major command
groups, you can use the following command:

GO MAJOR


Thanks to Andy Mahieu and Dedy Djajapermana and Club Tech iSeries Newsletter
Back

Sending Spooled Files To Another System

If you often need to send a spooled file to another system (e.g., an
iSeries or any other system that accepts File Transfer Protocol (FTP)
transfers), it's most common to use the SNDTCPSPLF (Send TCP/IP
Spooled File) command. There is another command, LPR (Send Spooled
File), that does exactly the same thing.

Here's an example of how to use it:

	LPR RMTSYS(*AUST2) PRTQ(QPRINT) +
	                   FILE(QPEZDISK) +
	                   JOB(123456/DOOHANS/QPADEV0004) +
	                   DESTTYP(*AS400)

This command sends the spooled file 'QPEZDISK' from user ID 'DOOHANS'
to the iSeries 'AUST2.'

Thanks to Sean Doohan
Back

Navigate Multiple CA Sessions More Quickly And Easily

You won't see any code in this tip, but if you're like most programmers who navigate
several Client Access sessions while programming, you'll find this tip useful.

It's not unusual for programmers to have many windows open at any given time. Those
that use SEU in Client Access sessions to edit their source frequently bounce from
one session to another. You can use the ALT TAB sequence to cycle through all of your
windows or you can create a shortcut that makes things simpler for you by cycling you
through Client Access sessions only.

In Client Access, select the option to customize your keyboard mapping. Pick a key
sequence (I like the Shift + End combination) that you want to use as your shortcut
and map it to the Jump Next function. Save your customization and from now on when
you want to navigate through your iSeries Client Access sessions, simply press your
shortcut key sequence.

Follow up:
The tip on Client Access navigation above generated considerable reader response.
Many of you wrote in to note that the Alt PageUp key sequence is the default keyboard
mapping for the Jump Next function in recent releases of Client Access. Others wrote
in to mention that another default key sequence in recent releases, Alt PageDown,
jumps to previous Client Access Sessions.

Still others prefer the Jump to Session function. This function lets you assign specific
sessions to specific key sequences so that you can navigate directly to sessions. For
example, some readers mentioned a scenario such as the following:

Session A is always an SEU edit session for QRPGLESrc.
Session B is always an SEU Session for QCLSrc.
Session C is always a session for WrkSplF.

The preferred keyboard mapping scheme follows:

Alt 1 -- Jump to Session A
Alt 2 -- Jump to Session B
Alt 3 -- Jump to Session C

I even received a note from a reader who customized the Client Access toolbar with
options for navigating between sessions! So many options...

Thanks to Club Tech iSeries Newsletter
Back

How to "squeeze" a field of spaces

	C                   Eval      string      =
	C                              'String   with    blanks     to  strip'

	C                   DoW       %Scan( '  ': string ) > 0
	C                   If        %Scan( '  ': string ) >
	C                              %Len( %TrimR( string ) )
	C                   Leave
	C                   EndIf
	C                   Eval      string      =
	C                              %Replace( '': string:
	C                                        %Scan( '  ': string ): 1 )
	C                   EndDo

	C     string        Dsply

Thanks to Gerry Tucker
Back

Get a command line from SysRq 3

	WRKMSGD CPX2313 (*MSGF) in library QCPFMSG.

Change DSPJOB to WRKJOB and you have a CMD-line, when doing a system request 3.

Back

Get Host by Name & Get Host by Address

	** Normally, I keep the following definitions as part of a larger
	** member containing the definitions for all of the sockets API,
	** called SOCKET_H, I copied the necessary stuff in-line to simplify
	** this example code.
	**
	** To compile:
	**   CRTBNDRPG PGM(xxxx) SRCFILE(xxxx/xxxx) DFTACTGRP(*NO) +
	**             ACTGRP(*CALLER)
	**   (actually, activation group can be whatever you prefer)
	**

	** Disclaimer:
	**    This program is meant to be an "example", to help explain a
	**    programming technique.  Although it works to the best of my
	**    knowledge, neither I, nor my employer will be held responsible
	**    for any damages that it may cause.

	** -------------------------------------------------------------------
	D* The "internet" address family.
	** -------------------------------------------------------------------
	D AF_INET         C                   CONST(2)

	** -------------------------------------------------------------------
	**    inet_addr()--Converts an address from dotted-decimal format
	**         to a 32-bit IP address.
	**
	**         unsigned long inet_addr(char *address_string)
	**
	**    Converts an IP address from format 192.168.0.100 to an
	**    unsigned long, such as hex x'C0A80064'.
	**
	**  returns -1 on error
	** -------------------------------------------------------------------
	D INet_Addr       PR            10U 0 ExtProc('inet_addr')
	D  char_addr                    16A

	** -------------------------------------------------------------------
	**    inet_ntoa()--Converts an address from 32-bit IP address to
	**         dotted-decimal format.
	**
	**         char *inet_ntoa(struct in_addr internet_address)
	**
	**    Converts from 32-bit to dotted decimal, such as, x'C0A80064'
	**    to '192.168.0.100'.  Will return -1 on error
	**
	** -------------------------------------------------------------------
	D inet_ntoa       PR              *   ExtProc('inet_ntoa')
	D  ulong_addr                   10U 0 VALUE

	** -------------------------------------------------------------------
	** "Special" IP Address values
	** -------------------------------------------------------------------
	D*                                                any address availabl
	D INADDR_ANY      C                   CONST(0)
	D*                                                broadcast
	D INADDR_BRO      C                   CONST(4294967295)
	D*                                                loopback/localhost
	D INADDR_LOO      C                   CONST(2130706433)
	D*                                                no address exists
	D INADDR_NON      C                   CONST(4294967295)

	** -------------------------------------------------------------------
	**   gethostbyname() -- Resolves a domain name to an IP address
	**
	**      struct hostent *gethostbyname(char *host_name)
	**
	**            struct hostent {
	**              char   *h_name;
	**              char   **h_aliases;
	**              int    h_addrtype;
	**              int    h_length;
	**              char   **h_addr_list;
	**            };
	**
	**   Returns a pointer to a host entry structure.  The aliases and
	**   address list items in the structure are pointers to arrays of
	**   pointers, which are null terminated.
	**
	** -------------------------------------------------------------------
	D GetHostNam      PR              *   extProc('gethostbyname')
	D  HostName                    256A

	** -------------------------------------------------------------------
	**    gethostbyaddr()--Get Host Information for IP Address
	**
	**     struct hostent *gethostbyaddr(char *host_address,
	**                                   int address_length,
	**                                   int address_type)
	**         struct hostent {
	**             char   *h_name;
	**             char   **h_aliases;
	**             int    h_addrtype;
	**             int    h_length;
	**             char   **h_addr_list;
	**         };
	**
	**     An IP address (32-bit integer formnat) goes in, and a
	**     hostent structure pops out.   Really, kinda fun, if you
	**     havent already learned to hate the hostent structure, that is.
	**
	** -------------------------------------------------------------------
	D GetHostAdr      PR              *   ExtProc('gethostbyaddr')
	D  IP_Address                   10U 0
	D  Addr_Len                     10I 0 VALUE
	D  Addr_Fam                     10I 0 VALUE

	** -------------------------------------------------------------------
	** Host Database Entry (for DNS lookups, etc)
	**
	**   (this is a partial implementation... didn't try to
	**    figure out how to deal with all possible addresses
	**    or all possible aliases for a host in RPG)
	**
	**            struct hostent {
	**              char   *h_name;
	**              char   **h_aliases;
	**              int    h_addrtype;
	**              int    h_length;
	**              char   **h_addr_list;
	**            };
	**
	**           #define h_addr   h_addr_list[0]
	** -------------------------------------------------------------------
	D p_hostent       S               *
	D hostent         DS                  Based(p_hostent)
	D   h_name                        *
	D   h_aliases                     *
	D   h_addrtype                   5I 0
	D   h_length                     5I 0
	D   h_addrlist                    *
	D p_h_addr        S               *   Based(h_addrlist)
	D h_addr          S             10U 0 Based(p_h_addr)

	D*** internal "work" variables. (not part of /COPY file)
	D wkInput         S            256A
	D wkIP            S             10U 0
	D wkLen           S             10I 0
	D p_Name          S               *   INZ(*NULL)
	D wkName          S            256A   BASED(p_name)

	C****************************************************************
	C* Parameters:
	C*
	C*   RetType:  May be *NAME or *ADDR.  If *NAME is given,
	C*       we'll return a domain name.  If *ADDR we'll return an
	C*       IP Address.
	C*
	C*   Input:   Host or IP address to lookup.  IP addresses should
	C*       be given in x.x.x.x format.
	C*
	C*   Output:  Resulting IP address, host name or error code.
	C*       Error codes are:  *TYPE = invalid "RetType" parameter.
	C*                         *BLANK = Input cant be blank
	C*                         *FAIL = Lookup failed for this host.
	C*
	C* Note:  This program is meant to be called from another
	C*     program, not from the command line.  If you're
	C*     intending to call it from the command line, I'd recommend
	C*     making a simple command and CL front-end.  (If you call
	C*     this program directly, OS/400 will mess up the long parms)
	C****************************************************************
	C     *entry        plist
	c                   parm                    RetType           5
	c                   parm                    Input           256
	c                   parm                    Output          256

	C* If we werent given enough parms, just end this program now...
	C*   (we'll seton LR, even)  We can't return an error since we
	c*   don't have an output parm to return it in (ack!)
	c                   if        %parms < 3
	c                   eval      *inlr = *on
	c                   return
	c                   endif

	C* Did we have a valid return type?
	c                   if        RetType <> '*NAME'
	c                               and RetType <> '*ADDR'
	c                   eval      Output = '*TYPE'
	c                   Return
	c                   endif

	C* Was some input given?
	C                   if        Input = *blanks
	c                   eval      Output = '*BLANK'
	c                   Return
	c                   endif

	C* were we given an IP address or a name?
	c                   eval      wkInput = %trim(Input) + x'00'
	c                   eval      wkIP = inet_addr(wkInput)

	C* An address was requested... and the input was already
	C*   an address...   return the input directly.
	C* (this is useful in an interactive application where the
	C* calling program might not know if the address typed was
	C* an IP address or a name)
	c                   if        RetType = '*ADDR'
	c                                and wkIP <> INADDR_NON
	c                   eval      Output = %trim(Input)
	c                   Return
	c                   endif

	C* Call the OS/400 resolver routines to get the information that
	C*  we require.  (It will check the hosts table first, then try DNS)
	c                   if        wkIP = INADDR_NON
	c                   eval      p_hostent = gethostnam(wkInput)
	c                   else
	c                   eval      p_hostent = gethostadr(wkIP:4:AF_INET)
	c                   endif

	c                   if        p_hostent = *NULL
	c                   eval      Output = '*FAIL'
	c                   return
	c                   endif

	C* if we're returning an address, we'll need to use inet_ntoa
	C*  to convert it back to dotted-decimal x.x.x.x format.
	C*
	c                   if        RetType = '*ADDR'

	c                   eval      p_name = inet_ntoa(h_addr)
	c                   if        p_name = *NULL
	c                   eval      Output = '*FAIL'
	c                   else
	c     x'00'         scan      wkName        wkLen
	c                   eval      Output = %subst(wkName:1:wkLen-1)
	c                   endif

	c                   return
	c                   endif

	C* the hostent structure contains a pointer to the requested
	C* domain name... we'll need to base a variable on that pointer,
	C* and then convert it from the "C" format for strings to a
	C* fixed-length RPG string
	c                   if        h_name = *NULL
	c                   eval      Output = '*FAIL'
	c                   return
	c                   endif

	c                   eval      p_name = h_name
	c     x'00'         scan      wkName        wkLen
	c                   eval      Output = %subst(wkName:1:wkLen-1)
	c                   return

Thanks to Scott Klement (I guess)
Back

CHKOBJ for an IFS file

Q:	Does anyone know of a command or technique, that could be used to check for
the existence of an IFS file like CHKOBJ can check for the existence of a DB file?

A:	As luck would have it, I wrote something like this a few months ago...
It's written in RPG IV, but designed to be used from CL commands in the same manner
that CHKOBJ is.

Member: CHKIFSOBJ CMD PROMPT('Check IFS Object') PARM KWD(OBJ) TYPE(*CHAR) LEN(120) MIN(1) + CHOICE('Path Name') PROMPT('Name of IFS + object') PARM KWD(AUT) TYPE(*CHAR) LEN(10) RSTD(*YES) + DFT(*NONE) VALUES(*NONE *EXCLUDE *RWX *RW + *RX *R *WX *W *X) PROMPT('Authority') Member: CHKIFSR4 ** This is called by the CHKIFSOBJ command to check for ** an object in the IFS, and see if the user has authority ** to that object. ** SCK 04/02/01 ** To compile: ** CRTBNDRPG PGM(xxx/CHKIFSR4) SRCFILE(xxx/xxx) DBGVIEW(*LIST) ** CRTCMD CMD(CHKIFSOBJ) PGM(xxx/CHKIFSR4) SRCFILE(xxx/xxxx) ** ** H DFTACTGRP(*NO) ACTGRP(*NEW) BNDDIR('QC2LE') D***************************************************************** D* Access mode flags for access() D* D* F_OK = File Exists D* R_OK = Read Access D* W_OK = Write Access D* X_OK = Execute or Search D***************************************************************** D F_OK C 0 D R_OK C 4 D W_OK C 2 D X_OK C 1 D*---------------------------------------------------------------- D* Determine file accessibility D* D* int access(const char *path, int amode) D* D*---------------------------------------------------------------- D access PR 10I 0 ExtProc('access') D Path * Value Options(*string) D amode 10I 0 Value D c_error PR D peErrMsg 128A const D error PR D peMsg 256A const D wkExists S 1N INZ(*OFF) D wkReadOk S 1N INZ(*OFF) D wkWriteOk S 1N INZ(*OFF) D wkSearchOk S 1N INZ(*OFF) D wkPos S 10I 0 D peObject S 120A D peAuthority S 10A c eval *inlr = *on C *entry plist c parm peObject c parm peAuthority C* We got parms, right? c if %parms < 2 c callp error('You must pass the OBJ ' + c ' and AUT parms to this command') c return c endif C* Validate AUT parm c if peAuthority <> '*NONE' c and peAuthority<>'*EXCLUDE' c and peAuthority<>'*ALL' c '*RWX' check peAuthority wkPos c if wkPos > 1 c and %subst(peAuthority:wkpos)<>*blanks c callp error('''' + peAuthority + ''' not ' + c 'valid for parameter AUT.') c return c endif c endif C* Figure out user's access to the file: c if access(%trim(peObject): F_OK) = 0 c eval wkExists = *On c if peAuthority <> '*NONE' c if access(%trim(peObject): R_OK) = 0 c eval wkReadOk = *On c endif c if access(%trim(peObject): W_OK) = 0 c eval wkWriteOk = *On c endif c if access(%trim(peObject): X_OK) = 0 c eval wkSearchOk = *On c endif c endif c else c callp c_error('access:') c endif C* For none, just see if the file exists: c if peAuthority = '*NONE' c if wkExists c return c else c callp error('No file found, or you' + c ' lack authority to it.') c endif c endif C* If any authority found, user isn't excluded: c if peAuthority = '*EXCLUDE' c if wkReadOk = *On c or wkWriteOk = *On c or wkSearchOk = *On c return c else c callp error('No file found, or you' + c ' lack authority to it.') c endif c endif C* Check read access: c if %scan('R': peAuthority: 2)>1 c and not wkReadOk c callp error('No file found, or you' + c ' lack authority to it.') c endif C* Check write access: c if %scan('W': peAuthority: 2)>1 c and not wkWriteOk c callp error('No file found, or you' + c ' lack authority to it.') c endif C* Check execute/search access: c if %scan('X': peAuthority: 2)>1 c and not wkSearchOk c callp error('No file found, or you' + c ' lack authority to it.') c endif c return *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Kill program and return an escape message that corresponds * to the current ILE C error number. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P c_error B D c_error PI D peErrMsg 128A const D geterrno PR * ExtProc('__errno') D strerror PR * ExtProc('strerror') D errno 10I 0 value D p_errno S * D errno S 10I 0 based(p_errno) c eval p_errno = geterrno c callp error(%trimr(peErrMsg)+' ' + c %str(strerror(errno))) P E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Kill program and return an escape message *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P error B D error PI D peMsg 256A const D SndPgmMsg PR ExtPgm('QMHSNDPM') D MessageID 7A Const D QualMsgF 20A Const D MsgData 256A Const D MsgDtaLen 10I 0 Const D MsgType 10A Const D CallStkEnt 10A Const D CallStkCnt 10I 0 Const D MessageKey 4A D ErrorCode 1A D dsEC DS D dsECBytesP 1 4I 0 inz(256) D dsECBytesA 5 8I 0 inz(0) D dsECMsgID 9 15 D dsECReserv 16 16 D dsECMsgDta 17 256 D wwMsgKey S 4A D wwMsg S 52A c callp SndPgmMsg('CPF9897': 'QCPFMSG *LIBL': c peMsg: %len(peMsg): '*ESCAPE': c '*PGMBDY': 1: wwMsgKey: dsEC) c if dsECBytesA > 0 c eval wwMsg = dsECMsgID + ' occurred ' + c 'calling QMHSNDPM API' c dsply wwMsg c endif c return P E Thanks to Scott Klement

Back

Debug a batch program

 1.	You will need to know what jobq the job will be running in so you can hold the
	jobq. Hold that job queue.

 2.	Submit the job that needs the debug.

 3.	Display the jobq to get the job name, user and job number.  (debug job)

 4.	Issue the command STRSRVJOB and F4. Enter the debug job information and
	press enter.

 5.	Issue the STRDBG command and F4 to enter the debug job information.

 6.	Release the jobq.

 7.	When the job start's to run a message will appear on the session that you
	entered the STRSRVJOB command.

 8.	Press F10 and at the command line enter your ADDBKP command.

 9.	To resume after the break points are entered press F3 and when back on the
	message press enter.
	This will allow the job to run in batch and will act like the interactive version
	of STRDBG.

10.	When the job ends remember to ENDDBG and ENDSRVJOB.

Thanks to 'unknown'

Here is a simple technique for capturing the information on a screen and using it in a program. My motivation for developing the program below was to save typing when using the STRSRVJOB command. I have recently had to debug many batch jobs. After having issued WRKJOB and typed "STRSRVJOB 123456/USERNAME/JOBNAME" a hundred times, I thought there must be some time-saving technique. The program below is designed to be invoked from the WRKJOB screen. This screen displays the job number, user, and job name; all required by many job-related commands, such as STRSRVJOB. The program works by capturing the screen image (using the Dynamic Screen Manager APIs), extracting the required job details, and invoking the appropriate command. The actual command string to be invoked is passed to the program by one of a number of commands. Command SRV passes the string 'STRSRVJOB'. Command PRTLOG passes the string 'DSPJOBLOG OPTION(*OUTPUT)'. The range of job-related commands could easily be extended. Indeed, almost any command could be invoked, given an appropriate screen to capture. Program GETSCR: H Dftactgrp(*NO) H BndDir('QSNAPI') * Dynamic Screen Manager prototypes... * Create input buffer: D CrtInpBuf pr 10i 0 Extproc('QsnCrtInpBuf') D InitSize 10i 0 Const D Increment 10i 0 Const Options(*OMIT) D Maximum 10i 0 Const Options(*OMIT) D InpBufHnd 10i 0 Const Options(*OMIT) D ErrorCode 272 Options(*OMIT) * Read screen without requiring AID key: D ReadScreen pr 10i 0 Extproc('QsnReadScr') D NoBytes 10i 0 Options(*OMIT) D InpBufHnd 10i 0 Const Options(*OMIT) D CmdBufHnd 10i 0 Const Options(*OMIT) D EnvHnd 10i 0 Const Options(*OMIT) D ErrorCode 272 Options(*OMIT) * Retrieve pointer to buffer data: D RtvDtaPtr pr * Extproc('QsnRtvDta') D InpBufHnd 10i 0 D Data * Options(*OMIT) D ErrorCode 272 Options(*OMIT) * Delete buffer: D DltBuf pr 10i 0 Extproc('QsnDltBuf') D BufHnd 10i 0 D ErrorCode 272 Options(*OMIT) D JobCommand s 32 D BufHnd s 10i 0 D NoBytes s 10i 0 D ReturnCode s 10i 0 D Screen ds Based(BufPtr) D JobName 169 178 D JobUser 192 201 D JobNo 217 222 D SelName s Like(JobName) D SelUser s Like(JobUser) D CmdStr s 66 D CmdLen s 15 5 Inz(%Size(CmdStr)) C *Entry Plist C Parm JobCommand * Create input buffer. C Eval BufHnd = CrtInpBuf (%Size(Screen) : *OMIT : C *OMIT : *OMIT : *OMIT) * Read screen. C Eval NoBytes = ReadScreen (*OMIT : BufHnd : C *OMIT : *OMIT : *OMIT) * Retrieve pointer to buffer data. C Eval BufPtr = RtvDtaPtr (BufHnd : *OMIT : *OMIT) * Build Command string. C x'00':' ' Xlate JobName SelName C x'00':' ' Xlate JobUser SelUser C Eval CmdStr = JobCommand + ' JOB(' + C JobNo + '/' + C %Trimr(SelUser) + '/' + C %Trimr(SelName) + ')' * Delete buffer. C Eval ReturnCode = DltBuf (BufHnd : *OMIT) * Execute command against job. C Call 'QCMDEXC' C Parm CmdStr C Parm CmdLen C Return The commands below should be created to invoke the program above: Command SRV: CMD PROMPT('Start Service Job') PARM KWD(COMMAND) TYPE(*CHAR) LEN(32) + CONSTANT(STRSRVJOB) Command PRTLOG: CMD PROMPT('Print Job Log') PARM KWD(COMMAND) TYPE(*CHAR) LEN(32) + CONSTANT('DSPJOBLOG OPTION(*PRINT)') Thanks to 'unknown'

Back

How File Overrides Really Work in ILE

When it comes to file overrides in ILE, there's plenty of speculation and
guesswork about how things work. Confusion about activation groups runs
rampant.

Because of this mass confusion, I dedicate an entire chapter to the subject
of file overrides in my book, "Starter Kit for the IBM iSeries and AS/400."
The information also appeared in "So You Think You Understand File
Overrides" in the July 2001 issue of NEWS/400. iSeries Network Professional
members can read the full article at

http://www.iseriesnetwork.com/artarchive/index.cfm?fuseaction=viewarticle&CO_
ContentID=10447&channel=art&subart=auth&authid=657 .

For now, I'd like to share with you the basic information you need in order
to understand just how file overrides work. With that, let's look at a few
rules that govern how file overrides are applied.

You're likely familiar with the fact that file overrides were affected by
the call levels within your job prior to the introduction of ILE. There is
an important call level rule that persists -- within a single call level,
only the most recent override is in effect. In other words, the most recent
override replaces the previous override in effect. This, coupled with the
fact that overrides are applied in decreasing call level sequence, explains
how overrides are applied in the OPM environment.

Things are a bit more complex in ILE because of the ways you can scope an
override. An override's scope determines the range of influence that the
override will have on your applications. You can scope an override to the
following levels:

* Call Level

    A call-level override is at the level of the process that issues the
    override, except that if the override is issued using a call to program
    QCmdExc, the call level is that of the process that called QCmdExc. A
    call-level override remains in effect from the time it is issued until
    the system replaces or deletes it or until the call level in which the
    override was issued ends.

* Activation Group Level

    An activation-group-level override applies to all programs running in
    the activation group associated with the issuing program, regardless of
    the call level in which the override is issued. In other words, only the
    most recently issued activation-group-level override is in effect. An
    activation-group-level override remains in effect from the time the
    override is issued until the system replaces it, deletes it, or deletes
    the activation group. These rules apply only if the override is issued
    from an activation group other than the default activation group.
    Activation-group-level overrides issued from the default activation
    group are scoped to call-level overrides.

* Job Level

    A job-level override applies to all programs running in the job,
    regardless of activation group or call level in which the override is
    issued. Only the most recently issued job-level override is in effect. A
    job-level override remains in effect from the time it is issued until
    the system replaces or deletes it or until the job in which the override
    was issued ends.

You specify an override's scope when you issue the override by using the
override command's OvrScope (Override scope) parameter.

ILE programs running in a named activation group can scope overrides to any
of these levels. However, programs (whether OPM or ILE) running in the
default activation group can scope overrides to only the job or call level.

The system processes the overrides for a file when it opens that file and
uses the following sequence to check and apply overrides:

  1. call-level overrides up to and including the call level of the oldest
     procedure in the activation group containing the file open (beginning
     with the call level that opens the file and progressing in decreasing
     call-level sequence)
  2. the most recent activation-group-level overrides for the activation
     group containing the file open (beginning with the call level that
     opens the file and progressing in decreasing call-level sequence)
  3. call-level overrides lower than the call level of the oldest procedure
     in the activation group containing the file open (beginning with the
     call level immediately preceding the call level of the oldest procedure
     in the activation group containing the file open and progressing in
     decreasing call-level sequence)
  4. the most recent job-level overrides (beginning with the call level that
     opens the file and progressing in decreasing call-level sequence)

If you remember nothing else about file overrides, remember this sequence of
applying overrides. I find that lack of knowledge of these steps accounts
for almost all file override problems as well as for the guesswork when
trying to determine or explain how file overrides really work.

Thanks to Club Tech iSeries Newsletter
Back

File Overrides Demystified

If you're like most, you've come across a situation in which the file overrides
in your application don't seem to be behaving the way you expect at one time or
another. After several attempts, you finally decide to change the override scope
to the job level so that the override you want will be in effect. While this
might produce the desired results, it's probably not the appropriate solution.
Application changes could result in unintentional behavior because of such a
widely scoped override.

The real answer is to understand how the system applies overrides. With that
understanding, not only can you achieve the desired behavior, but you can scope
also overrides appropriately. The rules governing the effect that overrides have
on your applications fall into three primary areas: the override scope,
overrides to the same file, and the order in which the system processes
overrides.

An override's scope determines the range of influence that the override will
have on your applications. You can scope an override to the following levels:

  * Call level
    A call-level override is at the level of the process that issues the
    override, except that if the override is issued using a call to program
    QCmdExc, the call level is that of the process that called QCmdExc. A call-
    level override remains in effect from the time it is issued until the
    system replaces or deletes it or until the call level in which the override
    was issued ends.

  * Activation group level
    An activation-group-level override applies to all programs running in the
    activation group associated with the issuing program, regardless of the call
    level in which the override is issued. In other words, only the most
    recently issued activation-group-level override is in effect. An activation-
    group-level override remains in effect from the time the override is issued
    until the system replaces it, deletes it, or deletes the activation group.
    These rules apply only if the override is issued from an activation group
    other than the default activation group. Activation-group-level overrides
    issued from the default activation group are scoped to call-level overrides.

  * Job level
    A job-level override applies to all programs running in the job, regardless
    of activation group or call level in which the override is issued. Only the
    most recently issued job-level override is in effect. A job-level override
    remains in effect from the time it is issued until the system replaces or
    deletes it or until the job in which the override was issued ends.

You specify an override's scope when you issue the override by using the
override command's OvrScope (Override scope) parameter.

One feature of call-level overrides is the ability to combine multiple overrides
for the same file so that each of the different overridden attributes applies.
Consider the following program fragments:

  ProgramA:

    OvrPrtF File(Report) OutQ(Sales01) OvrScope(*CallLvl)
    Call    Pgm(ProgramB)

  ProgramB:

    OvrPrtF File(Report) Copies(3) OvrScope(*CallLvl)
    Call    Pgm(PrintPgm)

When program PrintPgm opens and spools printer file Report, the overrides from
both programs are combined, resulting in the spooled file being placed in output
queue Sales01 with three copies set to print.

Now, consider the following program fragment:

  ProgramC:

    OvrPrtF File(Report) OutQ(Sales01) OvrScope(*CallLvl)
    OvrPrtF File(Report) Copies(3) OvrScope(*CallLvl)
    Call    Pgm(PrintPgm)

What do you think happens? You might expect this program to be functionally
equivalent to the two previous programs, but it isn't. Within a single call
level, only the most recent override is in effect. In other words, the most
recent override replaces the previous override in effect. In the case of
ProgramC, the Copies(3) override is in effect, but the OutQ(Sales01) override is
not. This feature provides a convenient way to replace an override within a
single call level without the need to first delete the previous override.

Consider the following program fragments:

  ProgramA:

    OvrPrtF File(Report) OutQ(Sales01) OvrScope(*CallLvl)
    TfrCtl  Pgm(ProgramB)

  ProgramB:

    OvrPrtF File(Report) Copies(3) OvrScope(*CallLvl)
    Call    Pgm(PrintPgm)

This latest change is identical to the first iteration of ProgramA and ProgramB,
except that rather than issue a Call to ProgramB from ProgramA, you use TfrCtl
to invoke ProgramB. It is noteworthy that TfrCtl doesn't start a new call level.
Instead, ProgramB will simply replace ProgramA on the call stack, thereby
running at the same call level as ProgramA. Because the call level doesn't
change, the overrides aren't combined. The point here is that contrary to common
belief, it's not the most recent override within a program that is in effect.
The rule is only the most recent override within a call level is in effect.

You've seen the rules concerning the applicability of overrides. In the course
of a job, many overrides may be issued. In fact, many may be issued for a single
file. When many overrides are issued for a single file, the system constructs a
single override from the overridden attributes in effect from all the overrides.
This type of override is called a merged override. Merged overrides aren't
simply the result of accumulating the different overridden file attributes,
though. The system must also modify or replace applicable attributes that have
been overridden multiple times, as well as remove overrides when an applicable
request to delete overrides is issued.

To determine the merged override, the system follows a distinct set of rules
that govern the order in which overrides are processed. You should first note
that the system processes the overrides for a file when it opens the file. The
system uses the following sequence to check and apply overrides:

  1. call-level overrides up to and including the call level of the oldest
     procedure in the activation group containing the file open (beginning with
     the call level that opens the file and progressing in decreasing call-level
     sequence)

  2. the most recent activation-group-level overrides for the activation group
     containing the file open

  3. call-level overrides lower than the call level of the oldest procedure in
     the activation group containing the file open (beginning with the call
     level immediately preceding the call level of the oldest procedure in the
     activation group containing the file open and progressing in decreasing
     call-level sequence)

  4. the most recent job-level overrides

For an example that demonstrates these rules, see "So You Think You Understand
File Overrides" in the July 2001 issue of iSeries NEWS. iSeries Network
Professional members can access the article online at
http://iseriesnetwork.com/resources/artarchive/index.cfm?fuseaction=viewarticle&CO_
ContentID=10447&channel=art&PageView=Search .

In some cases, you may want to protect an override from the effect of other
overrides to the same file. In other words, you want to ensure that an override
issued in a program is the override that will be applied when you open the
overridden file. You can protect an override from being changed by overrides
from lower call levels, the activation group level, and the job level by
specifying Secure(*Yes) on the override command.

This tip is excerpted from Chapter 21, "So You Think You Understand File
Overrides," in "Starter Kit for the IBM iSeries and AS/400" from 29th Street
Press (and the article which appeared in the July 2001 issue of iSeries

Thanks to Club Tech iSeries Newsletter
Back

Mean Absolute Deviation (MAD)

Assuming you mean MAD as defined by www.xycoon.com/mad.htm, here's an RPG
procedure to compute it.
Note that although it compiles, I haven't actually tried it with real data.
I'll leave the debugging (and conversion to a release prior to V5R3) up to you.

	P mad             b                   export
	D mad             pi             8f
	D   data                         8f   dim(1000) options(*varsize)
	D   num                         10i 0 value
	D sum             s              8f
	D avg             s              8f
	D i               s             10i 0
 	/free
    	// compute average
    	avg = %xfoot(%subarr(data:1:num)) / num;

    	// Compute sum of absolute deviations
    	sum = 0;
    	for i = 1 to num;
       		sum += %abs(data(i)-avg);
    	endfor;

    	// return mean average absolute devation
    	return sum / num;
 	/end-free
	P mad             e


Thanks to Hans Boldt
Back

V5R3: Changed Commands

I have seen on this forum a sustained interest for "What's new in V5R3".
The Information Center is of help in some extent (for example, you can get
a list of changed commands, but not directly the change itself).
Due to my job (I'm a teacher).  I have to track what really changed in the
OS/400 commands.  Hereunder is the result.

ADDIMGCLGE ... FROMFILE(*NEW) ... IMGSIZ
ADDOPTCTG ... MEDLOC(*IOSTATION/*MAGAZINE)
ADD/CHGPJE ... THDRSCAFN ... RSCAFNGRP
ADD/CHGRTGE ... THDRSCAFN ... RSCAFNGRP
ADDTRCFTR  ... JVATRG
APY/RMVJRNCHG  ... OBJ(*ALLJRNOBJ) ... FROMENTLRG ... TOENTLRG ... OPTION
               ... OBJERROPT ... OUTPUT ... OUTFILE ... OUTMBR ... DETAIL
CALLPRC ... PARM(&PARM *BYREF|*BYVAL)
CFGTCPAPP  APP(*NTP)
CHGATR  ATR(*CRTOBJSCAN, *SCAN, *ALWSAV, *RSTDRNMUNL, *SETUID, *SETGID)
CHG/CRTCRG ... EXITPGMFMT
CHG/CRTDEVPRT ... MFRTYPMDL(*INFOPRINT2085, *INFOPRINT2105)
CHGIMGCLGE ... WRTPTC
CHG/CRTJOBD ... DDMCNV
CHG/CRTJRN ... RCVSIZOPT(*MAXOPT3)
CHG/CRTLINETH ... ASSOCPORT
CHGNTPA ... SVRAUTOSTR ... SVRACTLOG ... SYNCRQD
CHG/CRTNWSD ... SHUTDTIMO ... PWRCTL
CHGOPTA ... EXTMEDFMT
CHG/CRTPSFCFG ... PDFADMIN ... PDFMAPPGM(*IBMPGM) ... PDFMAP ... AFPSAVE
              ... AFPOUTQ
CHGRCYAP SYSRCYTIME(min 1)
CHGSPLFA/DLT/HLD/RLS/WRKSPLF ... ASPDEV
CHGTCPA ... ECN
CHG/CRTUSRPRF ... LCLPWDMGT ... EIMASSOC
CHKOBJITG ... SCANFS
CMPJRNIMG/DSPJRN/RCVJRNE ... FROMENTLRG ... TOENTLRG ... CCIDLRG
CPYPTFGRP ... DTACPR ... CPYPTF ... RPLSPR ... COVER
CRTCTLxxx/DEVxxx/LINxxx etc ... AUT(*CHANGE)
CRTDEVCRP ... APPTYPE
CRTDIR/CRTUDFS ... CRTOBJSCAN ... RSTDRNMUNL
CRTDTAARA/DTAQ ... RMTLOCNAME(*RDB) ... RDB
CRTJRNRCV ... THRESHOLD(15000000)
CRTNWSSTG ... ASPDEV
CVTDIR OPTION(*CHGPTY) ... RUNPTY
DCL ... TYPE(*INT|*UINT)  LEN(2|4)
DCL ... TYPE(*CHAR) LEN(max 32767)
DCLF/RCVF/SNDF/SNDRCVF/WAIT ... OPNID
DLTUSRPRF ... EIMASSOC
DUPOPT ... ALWMEDERR ... OUTPUT
ENDSBS ... BCHTIMLMT
STR/ENDTCPSVR *DCE and *NSMI removed
INSPTF ... PMTMED
JAVA/RUNJVA ... AGTPGM ... AGTOPTIONS ... JOB
RCLSTG ... OMIT(*DIR)
RGZPFM ... RBDACCPTH ... ALWCANCEL ... LOCK
RMVJRNCHG ... TOENT(*COMMITSTART)
RMVMSG/SNDRPY ... RJTDFTRPY
RST ... PATTERN
RSTCFG ... OMITOBJ
RSTLIB ... SAVLIB(*ANY) ... OMITOBJ
RSTOBJ ... SAVLIB(*ANY|generic|list) ...  OMITLIB ... OMITOBJ
RTVJOBA ... DATETIME ... TIMZON ... TIMZONABBR ... TIMZONFULL ... TIMOFFSET
        ... THDRSCAFN ... RSCAFNGRP
RTVJRNE ... FROMENTLRG ... TOENTLRG ... CCIDLRG ... RTNSEQLRG
RTVUSRPRF ... LCLPWDMGT
RUNSQLSTM ... SQLCURRULE ... DECRESULT
SAV/SAVRST ... PATTERN ... SCAN
SAV/SAVOBJ/SAVLIB/SAVCHGOBJ/SAVCFG/SAVSECDTA/SAVDLO/SAVSYS
          ... DTACPR(*LOW/*MEDIUM/*HIGH)
SAVLIB/SAVOBJ/SAVCHGOBJ/SAVRSTLIB/SAVRSTOBJ/SAVRSTCHG ... ACCPTH(*SYSVAL)
SAVLIB ... SAVACTWAIT is a list of 3
STRCMNTRC/TRCCNN/TRCTCPAPP ... WCHMSG ... WCHMSGQ ... WCHJOB ... WCHLICLOG
                           ... WCHTIMO ... TRCPGM ... TRCPGMITV
STRTCP ... STRPTPPRF
STRTCPSVR ... NTPSRV
STRTRC ... TRCTYPE(*IFS) ... JOBTYPE ... WCHMSG ... WCHMSGQ ... WCHJOB
       ... WCHLICLOG ... WCHTIMO ... TRCPGM ... TRCPGMITV
TRCINT ... OUTPUT ... OUTFILE ... OUTMBR ... WCHMSG ... WCHMSGQ ... WCHJOB
       ... WCHLICLOG ... WCHTIMO ... TRCPGM ... TRCPGMITV


Thanks to Richard THEIS
Back

Page #2     Page #4

Back