Working with the IFS in RPG IV | ||
---|---|---|
Prev | Chapter 7. Working with directories |
One more program. This one shows how to read through directories "recursively."
As you know, a directory can contain sub-directories. Those sub-directories can contain more sub-directories. How can you write a program that will process them all, when you don't know how many levels deep it can go?
The answer is "recursion." Recursion is the ability for a sub-procedure to call itself. Each new call to the sub-procedure has it's own copies of the variables that it uses (unless they are global or static.) This is useful to us, since it means that we can write a sub-procedure that lists out the contents of a directory. When it finds a sub-directory in that directory, it can call itself with the sub-directory's name. The new copy of the sub-procedure will process that directory, again looking for more directories, etc.
It's a bit of a difficult concept to understand the first time you see it. It helps to think of it as a "call stack", like programs have. Each time the procedure calls itself, think of it adding a new entry to that stack. When the newly called procedure ends, it's entry on the stack is removed, and the copy that made the call continues where it left off.
Once again, we'll use QSHELL. But this time, instead of looking like an MS-DOS DIR command, we'll just print the filename to the screen. Hopefully, when you see what it's outputting, you'll understand how the recursion works.
* CH7RECURSE: Just in case that last example wasn't complicated * enough! * (From Chap 7) * * To compile: * CRTBNDRPG CH7RECURSE SRCFILE(xxx/QRPGLESRC) DBGVIEW(*LIST) * H DFTACTGRP(*NO) ACTGRP(*NEW) BNDDIR('QC2LE') BNDDIR('IFSTEXT') D/copy IFSEBOOK/QRPGLESRC,IFSIO_H D/copy IFSEBOOK/QRPGLESRC,ERRNO_H D/copy IFSEBOOK/QRPGLESRC,IFSTEXT_H D show_dir PR 10I 0 D curdir 1024A varying const D STDIN C CONST(0) D STDOUT C CONST(1) D STDERR C CONST(2) D S_ISDIR PR 1N D mode 10U 0 value D curr s 1024A D curdir s 1024A varying D line S 1024A D len S 10I 0 D cmd PR ExtPgm('QCMDEXC') D command 200A const D length 15P 5 const c eval *inlr = *on c callp cmd('DLYJOB DLY(10)': 20) C******************************************* C* Use the getcwd() API to find out the C* name of the current directory: C******************************************* c if getcwd(%addr(curr): %size(curr)) = *NULL c eval line = 'getcwd(): ' + c %str(strerror(errno)) c eval len = %len(%trimr(line)) c callp writeline(STDERR: %addr(line): len) c return c endif c eval curdir = %str(%addr(curr)) C******************************************* C* Call our show_dir proc to show all C* of the files (and subdirectories) in C* the current directory. C******************************************* c callp show_dir(curdir) c return *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * prints all of the files in the directory to STDOUT. * * if a subdirectory is found, this procedure will call * itself (recursively) to process that directory. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P show_dir B D show_dir PI 10I 0 D curdir 1024A varying const D mystat S like(statds) D dir S * D line S 1024A D len S 10I 0 D filename S 1024A varying D fnwithdir S 1024A varying D err S 10I 0 C******************************************* C* open the current directory: C******************************************* c eval dir = opendir(curdir) c if dir = *NULL c eval err = errno c eval line = 'opendir(): ' + c %str(strerror(err)) + c ', errno=' + %trim(%editc(err:'L')) c eval len = %len(%trimr(line)) c callp writeline(STDERR: %addr(line): len) c if err = EACCES c return 0 c else c return -1 c endif c endif c eval p_dirent = readdir(dir) c dow p_dirent <> *NULL c eval filename = %subst(d_name:1:d_namelen) c eval fnwithdir = curdir + '/' + filename c if filename<>'.' and filename<>'..' c if stat(fnwithdir: %addr(mystat))<0 c eval line = 'stat(): ' + c %str(strerror(errno)) c eval len = %len(%trimr(line)) c callp writeline(STDERR: %addr(line): len) c return -1 c endif c eval line = fnwithdir c eval len = %len(%trimr(line)) c callp writeline(STDOUT: %addr(line): len) c eval p_statds = %addr(mystat) c if S_ISDIR(st_mode) c if show_dir(fnwithdir) < 0 c return -1 c endif c endif c endif c eval p_dirent = readdir(dir) c enddo c callp closedir(dir) c return 0 P E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * This tests a file mode to see if a file is a directory. * * Here is the C code we're trying to duplicate: * #define _S_IFDIR 0040000 */ * #define S_ISDIR(mode) (((mode) & 0370000) == _S_IFDIR) * * 1) ((mode) & 0370000) takes the file's mode and performs a * bitwise AND with the octal constant 0370000. In binary, * that constant looks like: 00000000000000011111000000000000 * The effect of this code is to turn off all bits in the * mode, except those marked with a '1' in the binary bitmask. * * 2) ((result of #1) == _S_IFDIR) What this does is compare * the result of step 1, above with the _S_IFDIR, which * is defined to be the octal constant 0040000. In decimal, * that octal constant is 16384. *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ P S_ISDIR B D S_ISDIR PI 1N D mode 10U 0 value D DS D dirmode 1 4U 0 D byte1 1 1A D byte2 2 2A D byte3 3 3A D byte4 4 4A C* Turn off bits in the mode, as in step (1) above. c eval dirmode = mode c bitoff x'FF' byte1 c bitoff x'FE' byte2 c bitoff x'0F' byte3 c bitoff x'FF' byte4 C* Compare the result to 0040000, and return true or false. c if dirmode = 16384 c return *On c else c return *Off c endif P E /DEFINE ERRNO_LOAD_PROCEDURE /COPY IFSEBOOK/QRPGLESRC,ERRNO_H
Once again, you'll need to compile the program, start up QSHELL, and run it from the QSHELL prompt. The command that you'll run will be:
/QSYS.LIB/IFSEBOOK.LIB/CH7RECURSE.PGM