I have often said "Traditionally, us RPG programmers have written rather laborious RPG code using IBM *API's to read data from the IFS" but I just realised I have never actually added an example of the traditional laborious style code to this website.
Why Native APIs Matter
Sometimes you need direct control. Maybe you are processing large log files, reading configuration data, or working in an environment where SQL is not available or appropriate. The C runtime library gives you that direct access through a handful of simple functions.
IFSREAD shows you the pattern. Open the file. Read chunks of data. Process the bytes. Close the file. Handle every error along the way.
How to Call IFSREAD
The program accepts a single parameter: the full IFS path to the file you want to read.
Here is the basic call:
text
CALL IFSREAD PARM('/home/myuser/test.txt')A few more examples:
text
CALL IFSREAD PARM('/tmp/joblog.txt')
CALL IFSREAD PARM('/www/myapp/config.json')
CALL IFSREAD PARM('/home/nicklitten/roadtrip-notes.md')The program opens the file in read only text mode, reads it, displays each line with a line number, and tells you how many lines it found.
Sample Output
When you run it against a small text file you see something like this:
text
IFS File Reader
================
File: /home/myuser/test.txt
File opened successfully
Reading contents:
---
1: This is line one
2: This is line two
3: This is line three
---
File closed successfully
Total lines read: 3Clean and simple. Exactly what you need when you are debugging or inspecting a file on the IFS.
The Parameter
| Parameter | Type | Length | Description |
|---|---|---|---|
| pFilePath | CHAR | 256 | Full IFS path to the file you want to read |
Pass the path as a quoted string on the CALL command. The program uses options(*string) on the prototype so it handles the null termination for you.
The Core APIs Explained
IFSREAD relies on four C runtime functions. Here is how you declare them in RPGLE.
open()
This function opens a file and returns a file descriptor.
rpgle
dcl-pr open int(10) extproc('open');
path pointer value options(*string);
oflag int(10) value;
mode uns(10) value options(*nopass);
codepage uns(10) value options(*nopass);
end-pr;Flags you will use:
- O_RDONLY (value 1) opens the file for reading only
- O_TEXTDATA (value 16777216) tells the system to perform automatic CCSID conversion from the file's CCSID to your job CCSID
On success open returns a file descriptor greater than or equal to zero. On failure it returns -1 and sets errno.
read()
This reads data from an open file descriptor into your buffer.
rpgle
dcl-pr read int(10) extproc('read');
fildes int(10) value;
buf pointer value;
nbyte uns(10) value;
end-pr;You pass the file descriptor, a pointer to your buffer, and how many bytes you want to read. It returns the number of bytes actually read, zero at end of file, or -1 on error.
close()
Always close what you open.
rpgle
dcl-pr close int(10) extproc('close');
fildes int(10) value;
end-pr;Returns zero on success, -1 on error.
strerror()
When something goes wrong this turns the errno value into a human readable message.
rpgle
dcl-pr strerror pointer extproc('strerror');
errnum int(10) value;
end-pr;It returns a pointer to a null terminated string you can display with %str().
How the Program Works
Here is the flow in plain English.
- The program receives the file path parameter.
- It calls open() with O_RDONLY and O_TEXTDATA.
- If the file descriptor comes back as -1 it calls strerror() and ends.
- It then enters a loop that calls read() into a 4096 byte buffer.
- For each chunk it walks through the bytes one by one.
- When it finds a newline character (x'15' or x'25') it displays the line built so far with a line number.
- It keeps doing this until read() returns zero (end of file).
- Finally it calls close() and reports the total number of lines.
The 4K read buffer keeps I/O efficient. The 256 byte line buffer is big enough for most text lines. If a line is longer it simply truncates at 256 characters.
This single pass approach is fast and uses very little memory no matter how big the file is.
Error Handling Done Properly
Good code checks everything. IFSREAD does exactly that.
- After open() it checks whether the descriptor is less than zero.
- After every read() it checks for -1.
- When an error occurs it uses strerror() to show a meaningful message instead of just a cryptic number.
- It always attempts to close the file even if an error happened earlier.
Common errors you might see:
- File not found or path wrong
- Permission denied
- I/O error during read
- Path too long for the 256 character parameter
The program displays the type of failure plus the system message from strerror() and then ends cleanly.
Compiling the Program
You need to bind to the C runtime service program.
Using your favourite build tool (MAKEi or TOBi) it is usually as simple as:
text
gmake IFSREAD.PGMIf you are compiling manually:
cl
CRTRPGMOD MODULE(MYLIB/IFSREAD)
SRCSTMF('/your/ifs/path/IFSREAD.rpgle')
DBGVIEW(*SOURCE)
CRTPGM PGM(MYLIB/IFSREAD)
MODULE(MYLIB/IFSREAD)
BNDSRVPGM(QC2LE)
ACTGRP(*CALLER)The key points:
- Bind to QC2LE so the C runtime functions are available
- Use ACTGRP(*CALLER) for a utility program like this
- Make sure your user profile has *USE authority to the IFS file and all parent directories
Best Practices This Example Demonstrates
Here are the habits you should copy:
- Always close file descriptors. Leaking them is a fast way to run out of resources.
- Check every API return value. Never assume success.
- Use strerror() to turn errno into something a human can understand.
- Choose sensible buffer sizes. 4K for reading balances memory and I/O calls.
- Use O_TEXTDATA when you are reading text files so CCSID conversion happens automatically.
- Use *CALLER activation group for simple utility programs.
- Document your code. Future you will thank present you.
Limitations to Keep in Mind
This example keeps things simple on purpose:
- Line buffer is 256 characters. Longer lines are truncated.
- It only handles text files. Binary files will not display correctly.
- It is read only. There is a companion IFSWRITE example if you need to write.
- It processes one file per call.
- No directory walking. For that look at IFSDIR.
What Next?
Once you are comfortable with IFSREAD you can:
- Extend it to accept multiple files
- Add an option to output to another IFS file or a database table
- Build a service program wrapper so other programs can call these IFS functions easily
- Look at the related examples: IFSWRITE, IFSDIR, IFSSTAT and IFSAPI
Quick Reference
Need the prototypes in a hurry? Here they are again.
open, read, close and strerror as shown earlier in this lesson.
Flags: O_RDONLY = 1, O_TEXTDATA = 16777216
Read buffer: 4096 bytes Line buffer: 256 bytes
Newline characters handled: x'15' (NL) and x'25' (LF)
Summary
IFSREAD is a compact, well structured example of native IFS file reading on IBM i. It shows you the fundamental pattern: open, read in chunks, process, close, and handle every error properly.
Master this pattern and you will be able to tackle almost any IFS file task in pure RPGLE without reaching for SQL or external tools.
Now go and try it on one of your own files. Change the path, run it, and see the lines scroll past. Once it works, have a play with the buffer sizes or add a little extra error checking. That is how you really learn this stuff.
Happy coding and keep those file descriptors closed.
IFS READ - Read the IFS using native IBM-i API's
So, finally, here is a classic example of an RPG program simply reading the IFS using native IBM i API's. Enjoy!
///==============================================================================
/// Program: IFSREAD
/// Description: Classic IFS File Read Example Using Native IBM i APIs
///
/// Purpose:
/// - Demonstrate reading IFS files using native IBM i C runtime APIs
/// - Show proper error handling for IFS operations
/// - Display file contents line by line
/// - Illustrate best practices for IFS file handling
///
/// Features:
/// - Uses open(), read(), and close() APIs from ifsio_h
/// - Proper error checking with errno
/// - Buffer management for file reading
/// - Line-by-line processing of text files
/// - Clean resource management
///
/// Usage:
/// CALL IFSREAD PARM('/path/to/file.txt')
///
/// Parameters:
/// pFilePath - Full IFS path to file to read (up to 256 chars)
///
/// Example:
/// CALL IFSREAD PARM('/home/myuser/test.txt')
///
/// Modification History:
/// Date Author Description
/// ---------- ------------- --------------------------------------------------
/// 2021-01-03 Nick Litten Initial version - Classic IFS read example
///==============================================================================
ctl-opt dftactgrp(*no) actgrp(*caller)
bnddir('QC2LE')
copyright('1.0 - Read IFS File Using Native APIs');
// ---
// IFS API Prototypes
// ---
dcl-pr open int(10) extproc('open');
path pointer value options(*string);
oflag int(10) value;
mode uns(10) value options(*nopass);
codepage uns(10) value options(*nopass);
end-pr;
dcl-pr read int(10) extproc('read');
fildes int(10) value;
buf pointer value;
nbyte uns(10) value;
end-pr;
dcl-pr close int(10) extproc('close');
fildes int(10) value;
end-pr;
dcl-pr strerror pointer extproc('strerror');
errnum int(10) value;
end-pr;
// ---
// Constants
// ---
Dcl-C O_RDONLY 1; // Open for read only
Dcl-C O_TEXTDATA 16777216; // Text data conversion
Dcl-C BUFFER_SIZE 4096; // Read buffer size
// ---
// Global Variables
// ---
Dcl-S fd int(10); // File descriptor
Dcl-S bytesRead int(10); // Bytes read from file
Dcl-S buffer char(BUFFER_SIZE); // Read buffer
Dcl-S lineBuffer char(256); // Line processing buffer
Dcl-S linePos int(10); // Position in line buffer
Dcl-S totalLines int(10); // Total lines read
Dcl-S i int(10); // Loop counter
Dcl-S errorMsg char(256); // Error message
Dcl-S errorPtr pointer; // Pointer to error message
// ---
// Parameters
// ---
Dcl-Pi *n;
pFilePath char(256) const;
end-pi;
// ---
// Main Processing
// ---
// Initialize
fd = -1;
totalLines = 0;
linePos = 0;
lineBuffer = *blanks;
// Display header
dsply ('IFS File Reader');
dsply ('================');
dsply ('File: ' + %trim(pFilePath));
dsply *blank;
// Open the file
fd = open(%trim(pFilePath): O_RDONLY + O_TEXTDATA);
If (fd < 0);
errorPtr = strerror(errno);
errorMsg = %str(errorPtr);
dsply ('ERROR: Cannot open file');
dsply ('Reason: ' + %trim(errorMsg));
*inlr = *on;
Return;
EndIf;
dsply ('File opened successfully');
dsply ('Reading contents:');
dsply ('---';
// Read file contents
dow (bytesRead >= 0);
// Read chunk from file
bytesRead = read(fd: %addr(buffer): BUFFER_SIZE);
If (bytesRead < 0);
errorPtr = strerror(errno);
errorMsg = %str(errorPtr);
dsply ('ERROR: Read failed');
dsply ('Reason: ' + %trim(errorMsg));
leave;
EndIf;
If (bytesRead = 0);
// End of file - process any remaining line
If (linePos > 0);
totalLines += 1;
dsply (%char(totalLines) + ': ' + %subst(lineBuffer:1:linePos));
EndIf;
leave;
EndIf;
// Process buffer character by character
for i = 1 to bytesRead;
// Check for newline
If ((%subst(buffer:i:1) = x'15' or // Newline (EBCDIC)
%subst(buffer:i:1) = x'25';)) // Line feed
// Display the line
totalLines += 1;
If (linePos > 0);
dsply (%char(totalLines) + ': ' + %subst(lineBuffer:1:linePos));
Else;
dsply (%char(totalLines) + ': ');
EndIf;
// Reset line buffer
lineBuffer = *blanks;
linePos = 0;
Else;
// Add character to line buffer
If (linePos < %len(lineBuffer));
linePos += 1;
%subst(lineBuffer:linePos:1) = %subst(buffer:i:1);
EndIf;
EndIf;
endfor;
enddo;
// Close the file
If (fd >= 0);
close(fd);
dsply ('---';
dsply ('File closed successfully');
EndIf;
// Display summary
dsply *blank;
dsply ('Total lines read: ' + %char(totalLines));
*inlr = *on;
Return;
