Using RPG's traditional file input/output (I/O) operation codes to read files from the Integrated File System (IFS) is virtually impossible. The only way to do that today is to write a "driver" program that is used in conjunction with the SPECIAL device file. It would be beneficial to have integrated support for accessing all database systems and all file systems from within RPG, but we don't. So we have to make our own!
Fortunately, we can easily write the interfaces needed to access the IFS within RPG. Actually, although very little programming is required, there is a good bit of research required. But in the end, it works pretty well, so it's worth it.
It may seem as though the ongoing theme in several issues of Midrange Developer has been calling C language functions from within RPG IV. What is really happening is that IBM has exposed a number of APIs though the usual Qxxxxxx naming convention and also provided the C language with a callable function to do the same task (wrapping the API's complexities in some cases). Since the C language requires all functions to be callable via what's known as a "procedure pointer," IBM has had to externalize these C language functions. Therefore, it is often easier to call with the C language interface than the actual raw API itself. Remember, until Java came along, all the non-AS/400 programmers in the world were telling us how great C was and that it was the only language you need to use...so why not steal their functions and use them in good old RPG!
There are APIs, for example, that allow you to allocate memory dynamically while a program is running. In the C language, those APIs are wrapped by the C language runtime malloc(), realloc(), and free() functions. Until RPG IV recently added ALLOC, REALLOC, and DEALLOC to the language as native operation codes, you had a choice: call the OS/400 memory allocation APIs or call the much simpler C language functions.
The IFS is no different. The C language has a couple of options for accessing files stored in the IFS. You can open a file and read it in blocks--that is, read a specified number of bytes at a type. This is called byte I/O. You can also open an IFS file as a stream file, which is a different set of APIs, and use stream I/O to access the file. Stream I/O provides for record, or more accurately, line-at-a-time file access. You can read a line of a text file and write a line of a text file using stream I/O. If you use byte I/O, it is up to you to look at each input byte and decide what to do with it.
Normal IFS I/O (or byte I/O) has its advantages in that you can read both binary files, such as image files, and text files, such as HTML. Let's look at the APIs that support byte I/O first.
There are many file-oriented functions in the C language, but the I/O operations for the IFS include, but are not limited to, the following:
- fclose()--Close file
- feof()--Test for end-of-file indicator
- ferror()--Test for read/write errors
- fgetpos()--Get file position
- fgets()--Read a string
- fopen()--Open file
- fputs()--Write string
- fread()--Read a number of bytes from an IFS file
- fseek()--Reposition file cursor
- fsetpos()--Set file cursor
- fwrite()--Write bytes to IFS file
These functions, like all C language functions, are case-sensitive. They are all lowercase names. They will not work if called as anything other than fully lowercase names.
Note that each function begins with the letter "f." This stands, appropriately enough, for "file." If you look at the C compiler, however, you'll find that the native versions of these interfaces do not access the IFS. In fact, the C language compiler directive IFCTRL(*IFSIO) needs to be specified in order to access the IFS natively. So an attempt to call these interfaces directly from RPG IV will fail.
As it turns out, the compiler actually uses a different set of APIs to access the IFS file system when IFCTRL(*IFSIO) is specified. Fortunately, since the compiler needs to change the actual names of the procedures being called, the C source code is provided. A quick review of the C "header files" (as they're called) reveals that all the interfaces are translated to API names that are similar to the original names with the addition of a "_C_IFS_" prefix. That is, they are named _C_IFS_xxxxx, where xxxxx is the original procedure name. So fopen() is translated to _C_IFS_fopen, again a case-sensitive name--uppercase for "_C_IFS_" and lowercase for "fopen." These are the procedure names we can call to get to the IFS from within RPG IV.
So, under the new naming convention, the proper IFS API names needed to access the IFS include the following:
- _C_IFS_fclose()--Close file
- _C_IFS_feof()--Test for end-of-file indicator
- _C_IFS_ferror()--Test for read/write errors
- _C_IFS_fgetpos()--Get file position
- _C_IFS_fgets()--Read a string
- _C_IFS_fopen()--Open file
- _C_IFS_fputs()--Write string
- _C_IFS_fread()--Read a number of bytes from an IFS file
- _C_IFS_fseek()--Reposition file cursor
- _C_IFS_fsetpos()--Set file cursor
- _C_IFS_fwrite()--Write bytes to IFS file
- _C_IFS_fdopen()--Associates a file handle with an IFS file
- _C_IFS_fflush()--Flush out the write buffer to the file
- _C_IFS_fgetc()--Read a character
- _C_IFS_fileno()--Determine file handle associated with the IFS file
- _C_IFS_fprintf()--Write formatted data to a file
- _C_IFS_fputc()--Write a character
- _C_IFS_freopen()--File redirector
- _C_IFS_fscanf()--Read formatted data
- _C_IFS_ftell()--Get current file cursor position
For a complete list of the IFS APIs, see the source file member named IFS in the source file named H in library QSYSINC: SRCFILE(QSYSINC/H) MBR(IFS)
I've selected a few of the IFS APIs for review here. These allow basic file open, close, read, write, delete end-of-file, and file positioning operations. Remember, IFS files are not DB2/400 database files, so the meaning of update and delete are different. With IFS files, there is no update operation--just a write operation that overwrites existing bytes in the file.
Let's look at a few of the IFS APIs and create prototypes so they may be called directly from within RPG IV.
FILE* _C_IFS_fopen(const char* filename, const char* mode);
The first parameter is the name of the file in the IFS that is to be open. The second parameter specifies the open options for the file, such as read/write/modify.
In RPG IV, use the VALUE keyword for these parameters rather than use the CONST keyword. VALUE provides results similar to CONST, but it allows you to avoid using the %ADDR built-in function.
|
Figure 1: The ifsOpen prototype
The first parameter is the name of the file to be open, but since it is a C null terminated string, it will require the file name to have a hex x (X'00') immediately after the last non-blank character in the file name. So passing a literal name, such as '/www/index.html' is perfectly valid, but passing the name of the file within a 50-position field would require the addition of the %TRIMR operation. For example, the following are valid IFSopen statements:
|
Figure 2: Use of ifsOpen with a field and a literal
Line 1 declares a 255-byte character field that contains the name of the file (including its directory) on the IFS. Line 3 opens that file. Note the use of %TRIMR to eliminate the trailing blanks from the name. It is important use %TRIMR; otherwise, the file name will not be found. RPG's OPTIONS(*STRING) causes the value being passed to be converted into a null-terminated string. It does this by passing the value to the called function as a 256-position value and inserting X'00' in that extra position. So OPTIONS(*STRING) increases the passed length by one byte and fills that one extra byte with a null value.
Line 2 declares a pointer variable. When the IFS APIs open a file, a pointer to the file is returned. That file pointer is used by (i.e., passed to) all other IFS APIs that access the same file. So the pointer variable is similar to the file name on a regular File Description specification.
Line 4 in Figure 2 uses a quoted literal instead of a field name to open the IFS file. Note that since a quoted character string is being used, no %TRIMR is required.
To open a file on the IFS for read-only, use the RPG prototype IFSopen with the name of the file as the first parameter and set the mode option (second parameter) to 'r'. Figure 3 contains a list of the available mode options and combinations. Note that all modes must be specified in lowercase.
Open Mode | Description |
r | Open a text file for reading. The file must exist. |
w | Create a text file for writing. If the file exists, its contents are destroyed. |
a | Open a text file for writing at the end of the file. If the file doesn't exist, it is created. Does not destroy file contents, but also does not move the end-of-file marker. Use 'a+' for most append operations. |
r+ | Open a text file for reading and writing. The file must exist. |
w+ | Open a text file for reading and writing. If the file exists, its contents are destroyed. That is, the file is cleared. |
a+ | Open a text file for reading and appending; the appending operation includes the removal of the EOF marker before new data is written to the file, and the EOF marker is repositioned after writing is complete. If the file does not exist, it is created. |
rb | Open a binary file for reading. The file must exist. |
wb | Create an empty binary file for writing. If the file exists, its contents are cleared. That is, the file is virtually erased and recreated. |
ab | Open a binary file in append mode for writing at the end of the file. If the file doesn't exist, it is created. |
r+b or rb+ | Open a binary file for reading and writing. The file must exist. |
w+b or wb+ | Create an empty binary file for reading and writing. If the file exists, its contents are cleared. That is, the file is virtually erased and recreated. |
a+b or ab+ | Open a binary file in append mode for writing at the end of the file. If the file doesn't exist, it is created. |
Figure 3: ifsOpen mode settings
As indicated in Figure 3, use a lowercase "r" to open files for read-only. Use "a+" to open files for reading and writing (input/output).
Once an IFS file is open, it can be read from or written to (depending on the mode options). As the file is being written to, the file's position is adjusted to point to the end-of-file so that you are always writing to the end of the IFS file. Use the ifsSeek API to position the file to any byte position in the file.
|
Figure 4: ifsReadLine--Read a "record" from an IFS text file
Line 1 in Figure 4 prototypes the IFS fgets() procedure. This procedure reads data from the IFS file up to a carriage return/line feed sequence, which effectively reads a line or record from an IFS text file. Using this method--as opposed to using some of the other APIs that are available--makes it much easier to read text-based IFS files.
You can easily prototype the other IFS procedures so that they can be called from RPG IV.
The IFS is becoming more and more important to the AS/400 programmer. Saving and retrieving both formatted and unformatted data, as well as reading data sent to your iSeries from non-iSeries systems, means IFS file access is here to stay.
LATEST COMMENTS
MC Press Online