Update
Before using what is described below consider using SQL to write to IFS files.
After writing about how to read an IFS file in a RPG program I received a message from Domenico asking me how to do the opposite.
Very interesting article, as always, the only suggestion is: since, surfing internet, it is possible to find many examples about using _c_ifs_open but not one (!!) of _c_ifs_write .. it would be very very interesting and useful for our community.
Before I get started explaining how to write to an IFS file using RPG, let me say I am not going to repeat what I said in my earlier post A better way to read a file in the IFS with RPG, so you might want to review that before continuing with this post.
My goal is to write several strings to a file in the IFS. The first time I want to create the file and then write to it, the second I want to append data to the existing file.
Let me start with the definitions.
01 **free 02 ctl-opt option(*srcstmt) dftactgrp(*no) ; 03 dcl-pr OpenFile pointer extproc('_C_IFS_fopen') ; 04 *n pointer value ; // File name 05 *n pointer value ; // File mode 06 end-pr ; 07 dcl-pr WriteFile pointer extproc('_C_IFS_fputs') ; 08 *n pointer value ; // String to write 09 *n pointer value ; // Open mode 10 end-pr ; 11 dcl-pr CloseFile extproc('_C_IFS_fclose') ; 12 *n pointer value ; // Misc pointer 13 end-pr ; 14 dcl-s PathFile char(50) ; 15 dcl-s OpenMode char(100) ; 16 dcl-s FilePtr pointer inz ; 17 dcl-s SndData char(32767) ; 18 dcl-s i packed(3) ; |
Line 1: As I am writing in totally free RPG I need this at the top of my program. If you are unfamiliar with totally free RPG go check out the different flavors of free form RPG.
Line 2: I need the DFTACTGRP(*NO) as I am using external procedures.
Lines 3 – 6: Procedure prototype for the fopen, open file, external procedure.
Lines 7 – 10: This is the procedure for the write. Notice how it is fputs, which will write a string to an IFS file. As with the other APIs used it starts with _C_IFS_ then the API name. It has two parameters, both pointers to the string I want to write to the file and the mode in which the file was opened.
Lines 11 – 13: Procedure prototype for the procedure to close the IFS file.
Lines 14 – 18: These are the variables I will be using in the program. I won't bother to describe what each is for here, and you can probably guess their function from their names.
I want to create the file in the IFS before I write to it. Below is the code to do that:
19 PathFile = '/Simon/test_write.txt' + x'00' ; 20 OpenMode = 'w, o_ccsid=1252' + x'00' ; 21 FilePtr = OpenFile(%addr(PathFile):%addr(OpenMode)) ; 22 if (FilePtr = *null) ; 23 dsply ('Unable to open file (1)') ; 24 return ; 25 endif ; 26 CloseFile(%addr(PathFile)) ; |
Line 19: This is the folder in the IFS and the name of the file I want to create. I do need to "null terminate" the string with hex '00'.
Line 20: This is the mode in which I want to open the file. The "w" is lower case and means that the file must be opened in "write" mode. The o_cssid denotes the CCSID I want to create the file in. This is the part that took the longest for me to work out. On the IBM i I am using the "correct" CCSID is 1252, you may find that yours requires a different CCSID. Again I have to terminate the string with null.
Line 21: When the file is opened a new file is created, if there is one already the "w" indicates that the existing one will be overlaid with this new one. If there was an error opening the new file null is placed in FilePtr.
Lines 22 – 25: If there was an error a message is displayed and program is exited with a RETURN.
Line 26: I now want to close the file, as I do not want to write to it in CCSID 1265. I want to use my IBM i's default CCSID.
As I now have a file I am going to write to it.
27 OpenMode = 'w' + x'00' ; // Clear file, then write to file 28 FilePtr = OpenFile(%addr(PathFile):%addr(OpenMode)) ; 29 if (FilePtr = *null) ; 30 dsply ('Unable to open file (2)') ; 31 return ; 32 endif ; 33 for i = 1 to 10 ; 34 SndData = 'Write No. ' + %char(i) ; 35 WriteFile(%addr(SndData):FilePtr) ; 36 endfor ; 37 CloseFile(%addr(PathFile)) ; |
Line 27: My open mode is to be "write", "w", this means that if there is any data already within the file it will be deleted with the first write to the file. I have not given a CCSID here as I want to write to the file with the default CCSID.
Lines 28 – 32: Opens the file, and then handles the error if the file cannot be opened.
Lines 33 – 36: I perform this for loop ten times writing to the IFS file. The write has to use the address of a pointer to the variable SndData, and the pointer retrieved from the opening of the file, line 28.
Line 37: After I am done writing to the file I close it.
If I perform the section of code again it will not append records to the file, as I open it for "write" the file is cleared. To append to the file I need to open it as "append". All I need to do is to replace the "w" with "a" when I open the file.
27 OpenMode = 'a' + x'00' ; // Append to the file |
After opening the file as "append" if I perform the write to the file as I did before my IFS file will contain teo sets of records number 1 – 10.
I can even go and read the file using the program I created for the read an IFS file in a RPG program post to confirm that the data is good.
You can learn more about the fputs procedure from the IBM website here.
This article was written for IBM i 7.2, and should work for earlier releases too.
I've occasionally wondered about the performance of the "C" APIs, vs the "Unix" APIs for creating, changing, and working with IFS stream files.
ReplyDeleteSimon it is greatly appreciated that you are sharing on how to utilize files on the IFS from RPG programs.
ReplyDeleteHi Simon, How can I get the data to be written one line below each other? In your example it is all over the text document.
ReplyDeleteI am missing something simple!
It could be caused to the tool you are using to view the file with.
DeleteWhen I tested this and looked at the output file I produced, using Notepad, each record was on its own line.
Hi Simon,
ReplyDeleteDefine variables Pathfile and Openmode with options(*string) and use %str built-in function to avoid concat x'00' null string
This looks great. So in Totally Free how to I pass parameters to this nice little program. Like I would like to pass the filepath and the data to write to the file. I don't seem to be able to locate how to specify the parameters in this format.
ReplyDeleteHow to call a program in totally free RPG
DeleteThank you very much Simon. That is good to understand. But what I am asking it that in this post you explained how to write a file to IFS, which is what I want to do. So I want to create this program and pass it the file path and the content of the file. How do you specify parameters being passed?
DeleteAn I am only on 7.1, so I hope I have the required PTF's to utilize this totally Free format.
I have been looking through the rest of your blog, which is excellent. What I am looking for is how do you code lines 3 & 4 in Free format?
Delete01 **FREE
02 dcl-s Incoming char(30) ;
03 C *entry plist
04 C parm InComing
05 dsply Incoming ;
See the post Calling a program in totally free RPG
DeleteExcellent article, it helped me a lot!
ReplyDeleteHowever, I have a suggestion that would help people not familiar with fputs() (like me) - you would need to add a call of _C_IFS_fflush() to make sure the buffer is actually written to the output file.
(I've used a shorter variable SndData for writing to the file, so even after the program has ended the output file was empty. I had to add the call of _C_IFS_fflush just before the end.)
On the other hand, I need you advice on a topic: after I close the IFS file, I need to send it with SNDSMTPEMM and I receive the error message TCP5306 - Error from integrated file system API.
Cause . . . . . : There was an error received from integrated file system
API QlgOpen/dir_name/file_name.txt for file . The errno
was 3029. The return value was -1.
Recovery . . . : Issue CL command DSPMSGD CPE3029 for additional
information.
Message CPE3029 is "Resource busy".
The problem is that the file I've just created remained locked even after the _C_IFS_fclose operation and cannot be attached to the email. I would've expected the close operation would release the file, but not. The only way I could send it was to actually end the job and send it from another job.
Do you have any suggestion on how I can release the file with the program?
I've searched on Google for hours, I could not find anything. Possibly because I do not know what to look for :).
Thank you very much for your help.
In the mean time I found the solution for my issue and I want to share it with you, as you might want to change in the program:
Delete- the _C_IFS_fclose parameter needs to be the File Pointer (FilePtr) obtained at FileOpen, not %Addr(FilePath).
Thank you, Simon, for this site.