Writing to the IFS the Pure RPGLE Way with WRITEIFS2
Alright, let's get stuck in with the other side of the coin. Last time we looked at reading IFS files with native APIs. Today we are writing data from a database file out to the IFS using traditional C runtime functions, all in pure RPGLE with no SQL in sight.
WRITEIFS2 shows you how to take records from a physical file and write them to an IFS text file using open, write and close. It is a lower level approach that gives you full control over permissions, encoding and line endings.
What You Will Learn
By working through this lesson you will understand how to:
- Declare and use the C APIs open, write and close in free format RPGLE
- Read a physical file using native RPG Dcl-F and the read opcode with %eof
- Create or truncate an IFS file with specific permissions and CCSID
- Write data in chunks while managing line endings manually
- Handle errors properly with errno checking and on-error blocks
- Clean up file descriptors in all code paths, including exceptions
- Choose between the pure RPGLE approach and the simpler SQL method
Everything stays focused on practical IBM i development with RPGLE and CL.
Why Choose Pure RPGLE for IFS Writing
The SQL version (WRITEIFS) is quick and tidy when you have SQL available. But sometimes you want or need the pure RPGLE route.
Maybe you are on an older release, you do not want the SQL precompiler in your build, you need very specific file permissions, or you are working with binary data. WRITEIFS2 gives you that control.
You get to set the exact mode bits, choose the CCSID at creation time, and manage every byte that goes into the file.
How to Run WRITEIFS2
The program takes no parameters. Just call it.
text
CALL WRITEIFS2Before you run it you need:
- A physical file called MYFILE that contains a field called MYDATA (character or varchar works fine)
- The IFS directory /home/nicklitten/ to exist and be writable by your user profile
- Authority to read MYFILE and to create files in the target IFS directory
- The program bound to the QC2LE binding directory
What the Program Does
WRITEIFS2 opens MYFILE with native RPG I/O and reads every record in a simple do while not %eof loop.
For each record it builds a line of text, adds CRLF line endings so the file plays nicely with Windows editors, then writes the bytes to the IFS file using the write API.
When the database file is exhausted it closes the IFS file descriptor and displays a message telling you how many records were written.
The output file is created (or truncated) with permissions 0644, which means owner read and write, group read, everyone else read. The CCSID is set to 1208 so you get proper UTF-8 encoding.
The Main Code Pattern
Here is the heart of it in plain English.
You declare the input file the modern way:
rpgle
Dcl-F MYFILE disk(*ext) usage(*input) keyed;Then the read loop:
rpgle
read MYFILE;
dow not %eof(MYFILE);
// build output line from MYDATA field
// add CRLF
// write to IFS
read MYFILE;
enddo;Around that you open the IFS file once before the loop and close it once after.
The C APIs You Need
open()
You use this to create or open the target IFS file.
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;For writing we pass:
- O_WRONLY + O_CREAT + O_TRUNC + O_CCSID as the flags
- The mode bits for permissions (0644)
- The CCSID value 1208 for UTF-8
If open returns a negative number, something went wrong. Check errno.
write()
This writes your data buffer to the open file descriptor.
rpgle
dcl-pr write int(10) extproc('write');
fildes int(10) value;
buf pointer value;
nbyte uns(10) value;
end-pr;You pass the descriptor, a pointer to your data, and the length. It returns how many bytes were actually written or -1 on error.
close()
Never forget this one.
rpgle
dcl-pr close int(10) extproc('close');
fildes int(10) value;
end-pr;Call it when you are finished so the data is flushed and the descriptor is released.
File Permissions and CCSID
The mode calculation in the example is:
rpgle
mode = S_IRUSR + S_IWUSR + S_IRGRP + S_IROTH; // 0644You can change this if you need tighter or looser security. Common alternatives are shown in the original source comments.
The CCSID parameter on open tells the system what encoding to use when the file is created. 1208 gives you UTF-8, which is the sensible default for most modern text files.
Line Endings
Because we are writing text that might be read on Windows, the program adds CRLF (carriage return + line feed) at the end of each record. That is x'0D25' in EBCDIC or the equivalent after conversion.
If you only need Unix style LF you can change the ending bytes to just x'25'.
Error Handling and Cleanup
Good code expects things to go wrong. WRITEIFS2 checks the file descriptor after open and the return value after every write.
It also wraps the main logic in a monitor block so that if anything throws an exception the file descriptor still gets closed in the on-error section.
That is the pattern you want to copy: acquire the resource, use it, and guarantee cleanup no matter what happens.
errno is checked immediately after any C API failure so you can see the real reason (permission denied, no space, directory missing, etc.).
Compilation
Using CRTBNDRPG:
text
CRTBNDRPG PGM(NICKLITTEN/WRITEIFS2)
SRCSTMF('/home/nicklitten/source/writeifs2.rpgle')
DBGVIEW(*SOURCE)Or with your build tool:
text
make WRITEIFS2Remember to include QC2LE in the binding directory list so the C runtime functions resolve.
Customising the Example
You will almost certainly want to change a few things for your own use.
Change the output path:
rpgle
Dcl-S ifsFile varchar(255) inz('/home/nicklitten/myfile2.txt');Point the Dcl-F at your own physical file and field:
rpgle
Dcl-F MYFILE disk(*ext) usage(*input) keyed;Adjust the permissions if you need something different from 0644. The comments in the source show examples for 0600 and 0755.
WRITEIFS vs WRITEIFS2: Which Should You Use?
Here is a quick comparison to help you decide.
SQL approach (WRITEIFS) is great when:
- You want the shortest possible code
- You already have SQL in your build process
- You do not need to set explicit permissions
- You are happy with default line ending behaviour
Pure RPGLE approach (WRITEIFS2) wins when:
- You cannot or do not want to use the SQL precompiler
- You need full control over file creation flags and permissions
- You are writing binary data
- You are on a release before some of the SQL IFS services were added
- You prefer native RPG file I/O for the input side
- You want to avoid any SQL dependency in this particular program
Both approaches work. Pick the one that fits your environment and coding standards.
Best Practices Shown Here
- Always close the file descriptor, even when errors occur.
- Check every return code from the C APIs and react immediately.
- Use a monitor/on-error block for guaranteed cleanup.
- Write modern free format RPGLE with meaningful names.
- Use the right CCSID for the job (UTF-8 is usually best).
- Add CRLF when your consumers might be on Windows.
- Count what you process so you can report useful feedback.
- Document the mode bits so the next developer understands the security settings.
Common Problems and How to Fix Them
File not created? Check the directory exists, you have write authority, and look at errno in the job log.
Permission denied? Review the mode value and the directory permissions. Also check if the file already exists and is owned by someone else.
Write failures? Make sure the descriptor is valid, you have enough disk space, and the length you pass to write matches the data.
Database read errors? Confirm MYFILE is in your library list, the field MYDATA exists, and you have read authority.
Encoding looks wrong? Double check you used CCSID 1208 (or 0 for job CCSID) and that your data does not contain characters that do not convert cleanly.
Quick Reference
open flags used: O_WRONLY + O_CREAT + O_TRUNC + O_CCSID
Mode for 0644: S_IRUSR + S_IWUSR + S_IRGRP + S_IROTH
CCSID for UTF-8: 1208
Line ending added: CRLF
Input file: native Dcl-F + read + %eof
Cleanup: monitor block + on-error + close
Summary
WRITEIFS2 gives you a solid, self contained example of writing database content to the IFS using only pure RPGLE and the classic C runtime APIs. You get complete control over how the file is created and what ends up inside it.
Once you have this pattern working you can extend it to write multiple files, add headers and footers, handle different encodings, or even write binary structures.
Try it on one of your own files. Change the path and the Dcl-F declaration, run it, and check the IFS file that appears. Then experiment with the permissions or the line ending bytes. That hands on play is how these techniques really sink in.
Happy coding, and remember to close every file descriptor you open.
/// Program: WRITEIFS2
/// Description: Write data to IFS using traditional C APIs (Pure RPGLE)
///
/// Purpose:
/// - Demonstrate IFS file operations using C APIs (open/write/close)
/// - Show proper file descriptor management
/// - Illustrate file creation with permissions and CCSID
/// - Example of error handling with errno checking
/// - Pure RPGLE approach without SQL
///
/// Features:
/// - Uses native RPG file I/O to read database records
/// - Opens IFS file with C open() API
/// - Writes each record using write() API
/// - Proper file descriptor cleanup
/// - UTF-8 (CCSID 1208) output with explicit line endings
/// - Comprehensive error handling with errno
/// - Monitor/on-error exception handling
///
/// Usage:
/// CALL WRITEIFS2
///
/// Compile:
/// CRTBNDRPG PGM(NICKLITTEN/WRITEIFS2) +
/// SRCSTMF('/home/nicklitten/source/writeifs2.rpgle') +
/// DBGVIEW(*SOURCE)
///
/// Author: Nick Litten
///
/// Modification History:
/// 2026-06-03 V1.0 Initial creation - Nick Litten
/// ---
ctl-opt
main(WRITEIFS2)
option(*srcstmt:*nodebugio:*noshowcpy)
dftactgrp(*no) actgrp('NICKLITTEN')
bnddir('QC2LE')
copyright('V1.0 - Write data to IFS using traditional C APIs');
/INCLUDE '/modern/ifsio_h.rpgleinc'
/INCLUDE '/modern/errno_h.rpgleinc'
// Database file definition
Dcl-F MYFILE disk(*ext) usage(*input) keyed;
Dcl-Proc WRITEIFS2;
Dcl-Pi WRITEIFS2 end-pi;
// Status Message string for humans
Dcl-S stsMsg char(50);
// IFS Location where the data will be written
Dcl-S ifsFile varchar(255) inz('/home/nicklitten/myfile2.txt');
Dcl-S lineData varchar(257); // Data + CRLF
// File descriptor and operation variables
Dcl-S fd int(10) inz(-1);
Dcl-S flags int(10);
Dcl-S mode uns(10);
Dcl-S bytesWritten int(10);
Dcl-S recordCount int(10) inz(0);
monitor;
// Open IFS file for writing
// O_WRONLY = Write only
// O_CREAT = Create if doesn't exist
// O_TRUNC = Truncate to zero length if exists
// O_CCSID = Use CCSID parameter
flags = O_WRONLY + O_CREAT + O_TRUNC + O_CCSID;
// Mode: Owner=RW, Group=R, Public=R (0644 in octal)
mode = S_IRUSR + S_IWUSR + S_IRGRP + S_IROTH;
// Open file with UTF-8 encoding
fd = open(ifsFile: flags: mode: CP_UTF8);
If (fd < 0);
stsMsg = 'Failed to open IFS file';
dsply stsMsg;
dsply ('Error: ' + %char(errno()));
Return;
EndIf;
// Read all records from database file
read MYFILE;
dow (not %eof(MYFILE));
recordCount += 1;
// Add CRLF line ending (Windows-style)
lineData = %trim(MYDATA) + x'0D25';
// Write data to IFS file
bytesWritten = write(fd: %addr(lineData): %len(%trim(lineData)));
If (bytesWritten < 0);
stsMsg = 'Write failed';
dsply stsMsg;
dsply ('Error: ' + %char(errno()));
leave;
EndIf;
// Read next record
read MYFILE;
enddo;
// Close the IFS file
If (fd >= 0);
close(fd);
fd = -1;
EndIf;
stsMsg = 'Wrote ' + %char(recordCount) + ' records';
dsply stsMsg;
on-error ;
// Ensure file is closed on error
If (fd >= 0);
close(fd);
EndIf;
dump(a);
dsply ('*** WRITEIFS2 has failed!');
endmon ;
Return;
end-proc;
