RPGLE example reading from IFS (with traditional IBM-i API)

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: 3

Clean and simple. Exactly what you need when you are debugging or inspecting a file on the IFS.

The Parameter

ParameterTypeLengthDescription
pFilePathCHAR256Full 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.

  1. The program receives the file path parameter.
  2. It calls open() with O_RDONLY and O_TEXTDATA.
  3. If the file descriptor comes back as -1 it calls strerror() and ends.
  4. It then enters a loop that calls read() into a 4096 byte buffer.
  5. For each chunk it walks through the bytes one by one.
  6. When it finds a newline character (x'15' or x'25') it displays the line built so far with a line number.
  7. It keeps doing this until read() returns zero (end of file).
  8. 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.PGM

If 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:

  1. Always close file descriptors. Leaking them is a fast way to run out of resources.
  2. Check every API return value. Never assume success.
  3. Use strerror() to turn errno into something a human can understand.
  4. Choose sensible buffer sizes. 4K for reading balances memory and I/O calls.
  5. Use O_TEXTDATA when you are reading text files so CCSID conversion happens automatically.
  6. Use *CALLER activation group for simple utility programs.
  7. 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!

**FREE

///==============================================================================
/// 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;
{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>