IBM i / AS400 Decoder
| Field | Value |
|---|---|
| Type | Skill Resource |
| Source | ~/.copilot/skills/architecture/references/discovery/ibm-i-as400.md |
| Description | Not specified |
Source Content
IBM i / AS400 Decoder
A field guide for reading an IBM i (AS/400) codebase when you have never seen one. Read this before phase 2 of the playbook when the platform family is IBM i.
Contents
- What the platform actually is
- The object model (why there are no files and folders)
- How source shows up in a git repo
- RPG: the four flavors you will meet
- Reading fixed-format RPG
- The RPG cycle and indicators (the two biggest traps)
- DDS: physical, logical, display, and printer files
- CL (Control Language): the job glue
- DB2 for i
- Where state hides: data areas, data queues, journals
- ILE: modules, service programs, activation groups
- File-extension cheat sheet
What the platform actually is
IBM i is a business computer, not just an OS. Today’s name is IBM i running on Power Systems hardware; it was AS/400 (1988), then iSeries, then System i — the same lineage, and old-timers still say “the 400.” The OS was OS/400, then i5/OS, now IBM i. When someone says “we’re on the AS400,” they mean this stack.
Two facts explain almost everything that feels strange about it:
- The database is part of the operating system. DB2 for i is not a product you install; it is built in. Every “file” on the machine is a database object. There is no
/home/app/data.csv— there is a physical file object in a library. - The OS is object-based, not file-based. You do not navigate folders. You reference objects (each with a type like
*PGMor*FILE) inside libraries. A “library list” is the search path.
The object model
Everything is an object of a specific type. These are the ones you will see in a discovery:
*LIB — library
A container of objects (roughly a schema/namespace). QSYS holds the OS; app code lives in app libraries. The library list (*LIBL) is the ordered search path the job uses to resolve unqualified names.
*FILE — file object
The catch-all for data and screens. Sub-typed by its DDS: a physical file (table), logical file (view/index), display file (screen), or printer file (report). “File” on IBM i almost never means a stream file — it means a database or device object.
*PGM — program
A runnable object, compiled from RPG, COBOL, CL, or C. *MODULE and *SRVPGM are the ILE building blocks that bind into programs (see ILE).
*DTAARA — data area
A tiny named persistent value (a counter, a flag, a next-check-number). Small but business-critical — see Where state hides.
Other objects worth knowing
| Object | What it is | Why discovery cares |
|---|---|---|
*DTAQ | Data queue (async message queue) | Job-to-job integration; easy to miss |
*JOBQ | Job queue | Where submitted batch jobs wait |
*JOBD | Job description | Default library list + settings for a job |
*OUTQ | Output queue | Where spooled reports land |
*MSGF | Message file | Externalized error/prompt text |
*JRN | Journal | Transaction log / audit trail |
*CMD | Command | A custom green-screen command |
*USRSPC / *USRIDX | User space/index | Ad-hoc storage; rare but opaque when present |
How source shows up in a git repo
On the live machine, source code lives as members inside source physical files — QRPGLESRC, QCLSRC, QDDSSRC, and so on. Each member has a member type (RPGLE, CLLE, PF, DSPF…) that tells the compiler what it is. That member type — not the filename — is authoritative on the box.
When a team exports to Git (commonly via IBM’s Rational Developer for i / the ibmi-bob build tool), members become IFS stream files with extensions that mirror the member type: PAYCALC.RPGLE, CUSTMAST.PF, ORDENT.DSPF. That mapping is your fastest inventory signal — see the cheat sheet. Trust the extension, but confirm by reading the first few specs, because teams rename inconsistently.
RPG: the four flavors
RPG is not one language. The flavor sets how the code reads, so identify it before you read a single line of logic. From oldest to newest:
RPG III / RPG/400 (OPM, fixed-format)
Column-dependent, uppercase, and driven by the RPG cycle (see below). Lives in QRPGSRC, extension .rpg or .rpg36/.rpg38. If you see code where columns matter and there is no explicit main read loop, this is it. Most “scary old” AS400 code is here.
RPG IV / ILE RPG (fixed-format)
The 1994 rewrite. Still column-oriented on the C/F/D specs but far richer: real subprocedures, data structures, pointers, ILE binding. Extension .rpgle. Column meanings differ from RPG III — do not read RPG IV columns with an RPG III ruler.
Fully free-form RPG (**FREE)
Modern RPG that reads like Pascal/C — no column rules. A **FREE on line 1, or /free … /end-free blocks inside older code. Extension .rpgle. The easiest flavor to read.
SQLRPGLE (embedded SQL)
Any RPG flavor with EXEC SQL … END-EXEC blocks — SQL statements embedded in RPG. Extension .sqlrpgle. Treat the SQL as first-class business logic; it often replaces native record-level I/O.
Reading fixed-format RPG
Fixed-format lines start with a specification letter in a fixed column. Learn the specs and you can skim any RPG program:
| Spec | Column marker | Purpose |
|---|---|---|
H | Control | Compile options, program-wide settings |
F | File | Declares files used (input/output/update), device |
D | Definition | Variables, constants, data structures, prototypes |
I | Input | Input record field layout (older programs) |
C | Calculation | The actual logic — the main event |
O | Output | Output record layout (older programs) |
P | Procedure | Subprocedure boundaries (RPG IV) |
The opcodes you must recognize on C specs (and in free-form):
| Opcode | Meaning | Discovery note |
|---|---|---|
CHAIN | Random read by key | ”Look up one record” — the workhorse |
SETLL / SETGT | Position a cursor before reading | Precedes READE/READ loops |
READ / READE / READP | Sequential read / read-equal / read-prior | Loop over records |
WRITE / UPDATE / DELETE | Create / modify / remove a record | The CRUD verbs |
EVAL / Z-ADD / MOVE/MOVEL | Assignment (modern / numeric / legacy) | MOVE has surprising truncation rules |
IF/ELSE/ENDIF, DOW/DOU, SELECT/WHEN | Control flow | Standard |
EXSR / BEGSR/ENDSR | Call / define an internal subroutine | Rules cluster inside subroutines |
CALL/CALLP / KLIST/KFLD | Call a program / build a composite key | Cross-program calls; multi-field keys |
The RPG cycle and indicators
These two features trip up every newcomer. Flag them explicitly in the onboarding doc.
The RPG cycle is an implicit program-wide loop that older RPG (III especially) runs for you: it reads the primary file, processes a record, and repeats until end-of-file — without any loop you can see. So a program can “process every customer” with no visible READ loop. When *INLR (the Last Record indicator) is set on, the cycle ends and the program closes down. If you cannot find the loop, the loop is the cycle.
Indicators are 99 numbered on/off flags (*IN01–*IN99) plus named ones (*INLR, *INOF). They carry meaning by convention, not by name: *IN60 might mean “record not found” in one program and “print totals” in another. On display files they map to function keys and error states. Because a flag named *IN43 tells you nothing, indicator-heavy code is where business meaning is most hidden — trace every indicator to where it is set and tested.
DDS
DDS (Data Description Specifications) is the fixed-format language that defines files. Four sub-types matter, and each is a different thing:
PF — physical file (a table)
Defines the actual stored records: a record format (R), fields with types and lengths, and key fields (K). This is your table. Example decode: a field line PRICE 9 2P is a 9-digit, 2-decimal packed number. Field types: A char, P packed decimal, S zoned, B binary, L date, T time, Z timestamp. See references/data-model-extraction.md for the full decode.
LF — logical file (a view or index)
A key-sequenced and/or filtered access path over one or more PFs. It can re-order (different key), subset columns, SELECT/OMIT rows, or join multiple PFs (a join logical file). Logical files are how the old world did indexes and views — treat each one as a query the business cared about enough to persist.
DSPF — display file (a green screen)
Defines 5250 screens: record formats, field positions (row/column), function-key keywords (CAxx/CFxx), attributes (DSPATR), edit codes (EDTCDE), and subfiles (SFL/SFLCTL) — the scrollable list control. A DSPF is the closest thing to “the UI”; each record format is roughly one screen or panel. See references/mermaid-legacy-patterns.md for turning these into user-journey diagrams.
PRTF — printer file (a report)
Defines report layouts for spooled output. Often the only specification of what a report contains and how totals are computed, so read PRTFs when reverse-engineering reporting requirements.
CL (Control Language)
CL is the job-control / scripting language — the glue that runs programs, sets up files, and drives batch. Two flavors: CLP (OPM) and CLLE (ILE); read the same. Extensions .cl, .clp, .clle. Commands you will see constantly:
PGM/ENDPGM— program boundaries.DCL— declare a variable;DCLF— declare a file (usually a display file for a prompt screen).CALL— run another program (RPG/CL/COBOL), passing parameters.SBMJOB— submit a job to batch (to a*JOBQ). This is the fork point between interactive and batch — trace everySBMJOB.OVRDBF— override a file at runtime (redirect a program’s file to a different member/library). A huge source of “the code says X but production does Y.”MONMSG— monitor for a message/error and handle it (the CL equivalent of try/catch).RTVDTAARA/CHGDTAARA— read/write a data area (see below).CHGVAR,IF/ELSE,DOWHILE,GOTO— assignment and control flow.
DB2 for i
The integrated relational database. Two worlds coexist in the same tables:
- DDS-created files — older tables defined by a PF (as above). Field names are ≤10 chars, uppercase.
- SQL DDL —
CREATE TABLE/CREATE INDEX/CREATE VIEWin.sqlmembers. Modern code uses these; they can carry long names and constraints.
Both compile to the same kind of object, and a “file” and a “table” are the same thing seen two ways. Access is either native record-level I/O (RPG CHAIN/READ) or SQL (EXEC SQL, or set-based .sql). The catalog lives in QSYS2 (QSYS2.SYSTABLES, SYSCOLUMNS, SYSKEYS) — useful if you get a live connection to validate the reverse-engineered model. Watch for system naming (LIB/FILE) vs SQL naming (LIB.FILE); it changes how joins and qualifiers read.
Where state hides
On IBM i, important state lives outside the tables in three places newcomers miss:
Data areas (*DTAARA)
Named persistent scalars — the next invoice number, a “batch is running” flag, the current processing date, a company-wide config value. Read/written by RTVDTAARA/CHGDTAARA (CL) or IN/OUT (RPG). A single data area can gate an entire nightly run. The LDA (Local Data Area) is a per-job scratchpad passed between chained programs.
Data queues (*DTAQ)
FIFO/keyed async queues used to pass work between jobs — a lightweight message bus. If job A SNDDTAQs and job B RCVDTAQs, that is an integration point with no network in sight. Easy to miss and important for the dependency map.
Journals (*JRN)
The transaction log. Journaling is required for commitment control (transactions) and is the audit trail. If tables are journaled, you have a change history you can mine; if a rebuild needs audit parity, the journal defines the bar.
ILE
ILE (Integrated Language Environment) is the modern (RPG IV-era) compilation model. It matters for the call graph:
*MODULE— a compiled unit of one source member; not runnable alone.*PGM— one or more modules bound together into a runnable program (an entry point).*SRVPGM— a service program: a bound collection of reusable procedures (like a shared library / DLL). Programs call its exported procedures via a binding directory.- Activation group — the runtime scope for a program’s resources (variables, open files, commit cycle).
*NEWvs*CALLERvs named groups changes whether state and transactions are shared — relevant when reasoning about isolation.
For the call graph, a CALLP to a procedure may resolve into a *SRVPGM you must open separately; do not assume every callee is a standalone program.
File-extension cheat sheet
In an exported Git repo, classify by extension first (confirm by reading the header):
| Extension(s) | Member type | It is a… |
|---|---|---|
.rpg, .rpg36, .rpg38 | RPG / RPG36/38 | RPG III (fixed, cycle-driven) program |
.rpgle | RPGLE | RPG IV / free-form program |
.sqlrpgle | SQLRPGLE | RPG IV with embedded SQL |
.clp, .cl | CLP | OPM Control Language |
.clle, .cllesrc | CLLE | ILE Control Language |
.pf, .pf.dds | PF | Physical file (table) |
.lf, .lf.dds | LF | Logical file (view/index/join) |
.dspf, .dspf.dds | DSPF | Display file (green screen) |
.prtf, .prtf.dds | PRTF | Printer file (report) |
.sql, .table, .view | SQL | SQL DDL/DML |
.cbl, .cblle | CBL/CBLLE | COBOL (see references/mainframe-cobol.md) |
.cmd | CMD | Custom command definition |
.bnd, .bnddir | BND | Binder source / binding directory |
When an extension is missing or wrong, the first non-comment line usually reveals the type: **FREE/H/F/D specs → RPG; PGM → CL; A in column 6 with R record formats → DDS; IDENTIFICATION DIVISION → COBOL.