IFS and stream files on IBM i: the bridge to the outside world

Most IBM i developers spend their entire careers working with physical files and never touch the IFS. That is understandable — the traditional database model handles most of what a business application needs. But the moment you have to read a CSV dropped by a trading partner, write a JSON response for an API, generate a PDF report, or process an XML feed from an external system, you need the IFS. And if you have never used it before, it feels like a completely different operating system sitting inside the one you already know.

It is not that complicated. This post explains what the IFS is, how it is organised, and the practical patterns for reading and writing stream files from RPG.

What the IFS actually is

IFS stands for Integrated File System. It is IBM i’s hierarchical file system — the equivalent of the filesystem you use on Windows or Linux. It uses forward slashes, has directories and subdirectories, and stores files as streams of bytes rather than structured database records.

The traditional IBM i storage model (libraries, physical files, members) is still there. The IFS exists alongside it. From the IFS perspective, the traditional library structure is mounted at /QSYS.LIB. You can actually navigate to /QSYS.LIB/MYLIB.LIB/CUSTMAST.FILE and see your physical file from the IFS side.

The IFS root starts at /. Some important directories:

  • /home — home directories for users, like Linux
  • /tmp — temporary files
  • /QSYS.LIB — the traditional library structure, mounted here
  • /QOpenSys — UNIX-compatible filesystem, case-sensitive
  • /QDLS — Document Library Services, the old folder system (largely obsolete)

Most of the time you will be working under /home or a custom directory you create for your application — something like /myapp/input or /transfers/outbound.

Accessing the IFS from a green screen

Two commands you need to know:

WRKLNK (Work with Object Links) — the green-screen file browser for the IFS. Run it from any command line:

WRKLNK OBJ('/home/myuser')

This gives you a list of files and directories, similar to WRKOBJ but for the IFS. From here you can display, copy, delete, and rename files.

MKDIR — create a directory:

MKDIR DIR('/myapp/input')
MKDIR DIR('/myapp/output')

CPYTOSTMF / CPYFRMSTMF — copy between database files and stream files:

/* Copy a physical file member to an IFS stream file */
CPYTOSTMF FROMMBR('/QSYS.LIB/MYLIB.LIB/REPORT.FILE/DATA.MBR') +
           TOSTMF('/home/myuser/report.txt') +
           STMFOPT(*REPLACE) CVTDTA(*AUTO)

/* Copy an IFS stream file into a physical file member */
CPYFRMSTMF FROMSTMF('/home/myuser/inbound.csv') +
            TOMBR('/QSYS.LIB/MYLIB.LIB/INBOUND.FILE/DATA.MBR') +
            MBROPT(*REPLACE)

Reading a stream file from RPG

RPG accesses IFS files through the integrated file system APIs. The key ones are open(), read(), write(), and close() — the same POSIX file I/O functions used in C and other languages.

You prototype them in RPG like this:

**FREE
ctl-opt dftactgrp(*no) actgrp(*caller);

// POSIX file API prototypes
dcl-pr ifsOpen   int(10) extproc('open');
  path     pointer value options(*string);
  openflags int(10) value;
  mode      uns(10) value options(*nopass);
end-pr;

dcl-pr ifsRead   int(10) extproc('read');
  filedesc  int(10) value;
  buffer    pointer value;
  nbytes    uns(10) value;
end-pr;

dcl-pr ifsClose  int(10) extproc('close');
  filedesc  int(10) value;
end-pr;

// Open flags
dcl-c O_RDONLY  1;
dcl-c O_WRONLY  2;
dcl-c O_CREAT   8;
dcl-c O_TRUNC   64;

dcl-s FileDesc   int(10);
dcl-s Buffer     char(4096);
dcl-s BytesRead  int(10);
dcl-s FilePath   varchar(256);

FilePath = '/myapp/input/orders.csv';

// Open for reading
FileDesc = ifsOpen(%addr(FilePath) + 2 : O_RDONLY);

if FileDesc  0;
  // Process the buffer content
  // For line-by-line processing, scan for newline characters

  BytesRead = ifsRead(FileDesc : %addr(Buffer) : %size(Buffer));
enddo;

ifsClose(FileDesc);

*inlr = *on;
return;

For most practical work, reading in chunks and scanning for newlines is how you process text files line by line.

A cleaner approach: Scott Klement’s IFSIO service program

The raw POSIX API works, but it is verbose to set up. Scott Klement’s free IFSIO service program (widely used across the IBM i community) wraps these APIs into easier prototypes and adds useful helpers for line-by-line reading, character set conversion, and error handling.

If your shop already has it installed, use it. If not, the raw POSIX approach above works on any IBM i system with no dependencies.

Writing a stream file from RPG

Writing follows the same pattern — open with write flags, write your data, close:

**FREE
ctl-opt dftactgrp(*no) actgrp(*caller);

dcl-pr ifsOpen  int(10) extproc('open');
  path      pointer value options(*string);
  openflags int(10) value;
  mode      uns(10) value options(*nopass);
end-pr;

dcl-pr ifsWrite int(10) extproc('write');
  filedesc  int(10) value;
  buffer    pointer value;
  nbytes    uns(10) value;
end-pr;

dcl-pr ifsClose int(10) extproc('close');
  filedesc  int(10) value;
end-pr;

dcl-c O_WRONLY  2;
dcl-c O_CREAT   8;
dcl-c O_TRUNC   64;
dcl-c S_IRWXU   448;

dcl-s FileDesc  int(10);
dcl-s FilePath  varchar(256);
dcl-s LineOut   varchar(1024);
dcl-s CrLf      char(2) inz(x'0d25');  // EBCDIC newline

FilePath = '/myapp/output/report.txt';

// Open for writing, create if not exists, truncate if exists
FileDesc = ifsOpen(%addr(FilePath) + 2 : O_WRONLY + O_CREAT + O_TRUNC : S_IRWXU);

if FileDesc < 0;
  dsply 'Could not create file';
  *inlr = *on;
  return;
endif;

// Write a line
LineOut = 'EmployeeID,FirstName,LastName,Salary' + CrLf;
ifsWrite(FileDesc : %addr(LineOut) + 2 : %len(LineOut));

// Write more lines in a loop here...

ifsClose(FileDesc);

*inlr = *on;
return;

Character set conversion — the thing that trips everyone up

IBM i stores data in EBCDIC internally. IFS stream files are typically ASCII or UTF-8. When you write EBCDIC data to an IFS file without converting it, the file is unreadable on any other system.

The IFS APIs handle conversion through code pages. When you open a file, you can specify the coded character set identifier (CCSID):

dcl-pr ifsOpen_CCSID int(10) extproc('Qp0lOpen');
  path      pointer value options(*string);
  openflags int(10) value;
  mode      uns(10) value;
  ccsid     uns(10) value;
end-pr;

// Open with UTF-8 (CCSID 1208) — auto-converts on read/write
FileDesc = ifsOpen_CCSID(%addr(FilePath) + 2 : O_WRONLY + O_CREAT + O_TRUNC : S_IRWXU : 1208);

Common CCSIDs:

  • 1208 — UTF-8 (use this for files going to web services, modern systems)
  • 819 — ISO 8859-1 / Latin-1 (common for European partners)
  • 437 — ASCII (US)
  • 65535 — no conversion (raw bytes, use for binary files)

When in doubt, use UTF-8. It handles the full Unicode range and is what every modern system expects.

Practical patterns you will actually use

Check if a file exists before processing

dcl-pr ifsStat int(10) extproc('stat');
  path    pointer value options(*string);
  statbuf pointer value;
end-pr;

dcl-ds StatBuf len(144);
  dummy char(144);
end-ds;

dcl-s FilePath varchar(256);
dcl-s rc        int(10);

FilePath = '/myapp/input/orders.csv';
rc = ifsStat(%addr(FilePath) + 2 : %addr(StatBuf));

if rc = 0;
  // File exists, proceed
else;
  // File does not exist
endif;

Delete a file after processing

dcl-pr ifsUnlink int(10) extproc('unlink');
  path pointer value options(*string);
end-pr;

ifsUnlink(%addr(FilePath) + 2);

Rename / move a file

dcl-pr ifsRename int(10) extproc('rename');
  oldpath pointer value options(*string);
  newpath pointer value options(*string);
end-pr;

dcl-s OldPath varchar(256);
dcl-s NewPath varchar(256);

OldPath = '/myapp/input/orders.csv';
NewPath = '/myapp/archive/orders_20260517.csv';

ifsRename(%addr(OldPath) + 2 : %addr(NewPath) + 2);

This is the standard pattern for archiving processed files — rename them to an archive directory with a timestamp in the name.

Using SQL to work with IFS files

DB2 for i includes table functions for IFS operations that let you avoid the POSIX API entirely for some use cases.

Read a file’s contents directly in SQL:

SELECT CAST(LINE AS VARCHAR(1000))
  FROM TABLE(QSYS2.IFS_READ('/myapp/input/orders.csv')) AS F

This returns each line of the file as a row. You can then use SQL to filter, transform, and insert the data directly into a table — no RPG required for simple ETL jobs.

List files in a directory:

SELECT PATH_NAME, DATA_SIZE, LAST_USED_TIMESTAMP
  FROM TABLE(QSYS2.IFS_OBJECT_STATISTICS('/myapp/input', '*NO')) AS F
  WHERE OBJECT_TYPE = '*STMF'
  ORDER BY LAST_USED_TIMESTAMP DESC

When to use the IFS

The IFS is the right choice when:

  • You are exchanging files with external systems (CSV, XML, JSON, EDI)
  • You need to generate files for download (reports, exports)
  • You are building or consuming REST APIs (reading/writing JSON)
  • You need to store binary data — images, PDFs, attachments
  • You are running open-source tools (Node.js, Python, PHP) that expect a standard filesystem

For data that stays within your IBM i applications, physical files and DB2 tables remain the right model. The IFS is the bridge between that world and everything outside it.

Next post: Modern integrations — connecting IBM i to REST APIs, JSON, and the outside world.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top