All OS/2 processes have a limit on the number of "file handles", which represent files that can be open simultaneously. In OS/2 Warp this number is 20, but in previous versions of OS/2 the number has been higher under some circumstances. (And in Quercus Systems' Personal REXX for OS/2 the number is 50.) A few of these handles are usually allocated to system files like "standard input" and "standard output".
Sometimes a REXX program legitimately needs to open more than the current limit. Also, if REXX programs fail to close files when they are no longer needed, all available handles can be used up. In either case, a variety of strange symptoms can result and the program will fail to operate as expected.
Some of the symptoms that can occur:
If symptoms like these, or other problems involving I/O directly or indirectly, should occur, first verify that your program has closed all files it no longer needs to keep open. This can be done with the STREAM, LINEOUT, or CHAROUT functions (although there have been reports that the latter two do not always work in IBM's OS/2 REXX).
If you have determined that all unnecessary files have been closed and that you need to have many files open simultaneously, REXXLIB's DosFileHandles function can help. If it is called with no arguments, it returns the current limit on the number of handles. It can also be called with a numeric value to raise or lower the limit:
current = DosFileHandles(40) say "Maximum of 40 file handles now available." say "Limit was formerly" current
It is possible to sort REXX arrays (i. e. compound variables with positive integral indices) with native REXX facilties, but it is not easy. First, you have to know exactly how to code a sorting algorithm - and algorithms that are significantly faster than the simple "bubble sort" are usually very tricky. Second, it is very inconvenient to pass arrays to subroutines in REXX. Third, you must copy the appropriate code into every program that needs to sort - because it isn't possible to pass an array to an external procedure at all (unless you use a file or external data queue).
REXXLIB's ArraySort routine takes care of all these problems, and provides many sorting options. If you think of each element of the array as a record, then ArraySort can handle multiple fields in each record. On each field it can sort in ascending or descending order where the order is either strictly alphanumeric, case-insensitive alphanumeric, or based on REXX arithmetic comparisons.
For instance, the following code sorts data first on year and then on month:
data.1 = '1995 March California' data.2 = '1994 October Connecticut' data.3 = '1993 January Massachusetts' data.0 = 3 /* number of individual records */ call arraysort 'data.', 1, data.0, 1, 4, 'a', 'n', 6, 9, 'a', 'c'
REXX has an exponentiation operator (**), but it allows only integral exponents. This means there is no native REXX way to take a square root (exponent 1/2), let alone any other kind of root or more general power. Other standard mathematical functions like logarithms and trigonometric functions are also missing from REXX.
REXXLIB fills in this gap by providing:
People tend to write dates in a wide variety of formats, as is evidenced by the fact that REXX's Date function can return about 10 different formats. Some format conversions are simple, but others require very complex algorithms, even for finding the day of the week for a given calendar date. However, the REXX language provides no built-in method for converting formats.
Another common need is for a way to compute the calendar date 120 days from today (for example). This too is complicated to handle, when year boundaries and leap years must be dealt with. REXX does have one date format, called the "base date" - the number of days since January 1, 1 - that makes such computations easy - provided you can convert to and from this format. REXXLIB provides the DateConv function to handle this problem. Here's how to display the date 120 days from today, for example:
today = date('b') /* today's date in basedate format */
say '120 days from today is' dateconv(today+120, 'b', 'n')
Suppose you want to be able to determine whether a given program is already running. This might be in order to avoid having two copies running at the same time, to start the program if it is not running, or to stop it if it is.
REXXLIB provides two functions to address this need. One, DosPidList produces a list of active processes in the system ("PID" stands for Process ID). You can get a list of all process IDs in the system, along with the name of the executable file associated with the process, the ID of the parent process, and the ID of the "session" in which it is running. Normally, you would use the name of the executable file to identify the process you are looking for. The following code is an example:
progname = 'C:\UTILITY\EDITOR.EXE'
call dospidlist 'pids', 'names'
do i = 1 to pids.0
if upper(names.i) = progname then do
say progname 'is running with PID' pids.i
leave
end
end
progname = 'C:\UTILITY\EDITOR.EXE'
call dospidlist 'pids', 'names'
if arraysearch('names', 'list', progname, 'x') \= 0 then do
index = list.1
say progname 'is running with PID' pids.index
end
list.
The option 'x' in the function call specifies that the
search should be for an
exact match, except for case.
Sometimes you may find it easier to search by the session title instead of the program name. (The session title is usually the contents of the main window title bar.) REXXLIB provides another function, DosSwitchList which can produce a list of all session titles. This can be used to search for sessions containing DOS or Windows programs or OS/2 command files, which DosPidList misses. You can use this function to find out the title of the current session:
call dosswitchlist 'titles', 'pids' call arraysearch 'pids', 'list', dospid(), 'x' index = list.1 say 'Current session title is:' title.index
If you have several versions of OS/2 on your hard disk because you are using the Boot Manager, you have to find out which of them is the current boot drive in order to find important files like CONFIG.SYS, STARTUP.CMD, or AUTOEXEC.BAT, or system directories such as \OS2. This is very easy with the DosBootDrive function:
say "OS/2 boot drive is" dosbootdrive"."
Unless a REXX program is run from the command line, it is necessary to start a new command shell (usually CMD.EXE) when an "internal" command like COPY is invoked from a REXX program. This is the case when using a program made with VX-REXX or VisPro/REXX, when the program is run from PMREXX, or for any REXX application script. Starting a new copy of the shell has significant overhead. Also, the return code of the COPY command is lost when it is run from a new shell. (Similar problems attend the use of commands like DIR, CD, ERASE, RENAME, and ATTRIB, which is one reason both REXXLIB and IBM's REXXUTIL provide alternative functions.)
There is an additional problem with the standard COPY command, in that by default it replaces the target file if it already exists, with no warning. It is easy enough to copy a file in REXX just by reading it all with CHARIN and writing it back out with CHAROUT, but this has the problem that it does not preserve extended attributes.
REXXLIB provides a way around all of these problems with the DosCopy function. It's also easy to append one file to another:
call doscopy 'newdata', 'oldfile', 'a'
All programs that access files whose names are supplied by online users or from a configuration file - in other words most programs - may be asked to read or write a file on a removable medium like a floppy disk or CD-ROM. Although OS/2 will pop up a dialog box if such a file is accessed without a disk in the drive, this leaves the application little or no control over the situation, and is clearly undesirable for unattended operation.
Two REXXLIB functions make it possible to test whether a given drive actually contains a disk in it (without risking a popup):
if dosdisk('a:', 'b') = -1 then
say 'Insert disk in drive A:'
IBM's REXXUTIL package provides the SysGetEA function to read the extended attributes of a file, but you must know the name of the EA in advance - there is no means for determining what EAs are associated with the file. REXXLIB's DosEAList function allows you to obtain the complete list of a file's extended attributes. You may optionally also get the corresponding values of each EA, as well as an associated flag byte.
REXXLIB has another function, DosEASize, which gives the total size of all EA data for a file. This is handy for determining whether a file has any extended attributes.
Here is a complete program that lists all extended attributes of a file:
/* test ealist function */
types. = ''
types.FFFE = "length-preceeded binary"
types.FFFD = "length-preceeded ASCII"
types.FFFB = "length-preceeded bitmap"
types.FFFA = "length-preceeded metafile"
types.FFF9 = "length-preceeded icon"
types.FFEE = "length-preceeded ASCII"
types.FFDF = "multi-valued, multi-typed field"
types.FFDE = "multi-valued, single-typed field"
types.FFDD = "ASN.1 field"
say ''
parse arg file
file = strip(file)
i = dosealist(file, "name.", "value.", "flags.")
if i > 0 then do
say i "extended attributes of" file':'
do j = 1 to i
say name.j
parse var value.j type.j 3 length.j 5 value1.j
type = c2x(reverse(type.j))
length = c2d(reverse(length.j))
say ' Type:' type 'Length:' length 'Flags:' c2x(flags.j)
select
when type = 'FFFD' | type = 'FFEE' then /* length-preceded ascii */
say ' Value:' left(value1.j, 40)
when type = 'FFFE' | type = 'FFF9' | type = 'FFFA' | ,
type = 'FFFB' then do /* other length-preceded data */
l = min(20, length(value1.j))
say ' Type format:' types.type
say ' Value:' c2x(left(value1.j, l))
end
when type = 'FFDD' | type = 'FFDE' | type = 'FFDF'
then do /* other recognized type */
l = min(20, length(value.j)-2)
say ' Type format:' types.type
say ' Value:' c2x(substr(value.j, 3, l))
end
otherwise do
l = min(20, length(value.j))
say ' Unrecognized type'
say ' Value:' c2x(left(value.j, l))
end
end
end
end
else
say "DosEAList failed, rc =" i "on '"file"'"
You can obtain the value of a single environment variable, if you know it's name, with the REXX VALUE function (or the REXXLIB DosEnv function). However, if the name is not fully known ahead of time, it is convenient to be able to get a list of all environment variables. For example, a variable number of file names might be saved in environment variables whose names begin with the string "MY-APP-FILE". (This is a simpler alternative to storing all the names in one long variable, separated by ";".)
REXXLIB's DosEnvList function could be used to find all such items:
call dosenvlist 'list'
call arraysearch 'list', 'results', 'my-app-file', 'f'
do i = 1 to results.0
item = results.i
say "Application file:" list.item
end
There are times when one does not want to change a file's date even though the file itself has been updated, or when one wants to give a file a new date without changing it. For instance, the OS/2 COPY command itself behaves this way (assigning the date of the original file to the copy). Or perhaps it is desired to assign a single date to a group of files being distributed together.
This is easy to do with REXXLIB's DosFDate function. For example, here is how to give the date/time of one file to another (which may have been derived from the first):
parse value dosdir(file1, 'dt') with date time date = dateconv(date, 'u', 's') time = space(time, 0, ':') call dosfdate file2, date, time
There are probably three main situations in which one would want to change a file's size. The first is when the file is going to be completely replaced from the beginning. You could first either delete it entirely with the DosDel function, or just set it to 0 length with the DosCreat function. If you are going to be rewriting an existing file, you generally need to do this, since otherwise the standard CHAROUT and LINEOUT functions append data to the end of a file.
The second situation is when you want to pre-allocate space for a file without actually filling it with data. This might be done to try to avoid disk fragmentation, or simply to help ensure that there is enough disk space available to write the file at a later time. You can use the DosFSize function to do this:
call doscreat newfile /* create the file */ call dosfsize newfile, 1024*1024 /* allocate a megabyte */
/* "pos1" is the position of the beginning of the part to be removed, "pos2" is the position of the end of the part. */ filesize = dosfsize(filename) temp = charin(filename, pos2, filesize - pos2) call dosfsize filename, pos1 - 1 call charout filename, temp call charout filename
Unlike the old "FAT" file system, IBM's enhanced HPFS file system records the date and time a file was created and the date and time it was last read, in addition to the date and time it was last updated. However, none of the usual OS/2 commands, like DIR or IBM's REXXUTIL SysFileTree function, make it possible to access this information. But REXXLIB's DosFileInfo function does:
say filename 'was created on' dosfileinfo(filename, 'c')',' say 'last modified on' dosfileinfo(filename, 'w')', and' say 'last accessed on' dosfileinfo(filename, 'a')'.'
Carefully programmed applications that work with files and directories should test whether file or directory names that are supplied from an online user, external configuration file, etc. are suitable. In particular, if a name should refer to a file, the program should check it is not a directory, and vice versa.
It is possible to make this test with IBM's SysFileTree function by searching for a given name and restricting the search to directories. But one pitfall of this occurs if the supplied name contains wildcard characters, since then the supplied name isn't really a valid directory name at all, even though one or more existing directories might match. It's easy enough to separately exclude names with wildcards, but it's even easier to use REXXLIB's DosIsDir function:
if \dosisdir(name) then
say '"'name'" is not a valid directory name.'
OS/2 named pipes offer a convenient tool for interprocess communication, since they can be read and written sequentially like files. This makes it possible to communicate, even across a network, with programs that don't otherwise have the ability to communicate with other programs - such as command line oriented DOS programs.
Named pipes can also be used as a general interprocess communication technique for building client-server applications that implement either client or server (or both) in REXX. Such applications can operate across a network, although if they need only be run on a single computer, REXX offers somewhat simpler IPC techniques, such as external data queues.
REXXLIB offers a number of functions for using named pipes:
Any time you have a "resource" such as a file that can safely be used by only one process at a time, it is wise to take some steps to prevent concurrent usage. A sequential file such as a log file that might be updated periodically by several processes is a good example. (If a second program tries to write to the file while it is in use by another program, the second program's attempt to write will probably fail, instead of simply waiting until the file is available.)
Database files that are managed by a single server tend to be well protected, but if you were to implement a database-like application where file updating is done from different REXX programs, you have another good example. In this case, you would probably want to make potential file readers, as well as writers, wait until a group of related updates are complete. (And not simply fail because the file is "in use".)
OS/2 provides "mutual exclusion semaphores" to allow for controlling access to any kind of resource, and REXXLIB provides a number of functions for using such semaphores:
It is not hard to implement "client-server" applications in REXX, and OS/2 provides a number of "interprocess communication" tools to help. Named pipes (see Using named pipes), and REXX "external data queues" are two examples. These are fairly high-level tools, but sometimes lower level facilities are warranted.
A "server" is simply a process that is capable of performing one or more sorts of work upon request. This might involve updating a file, using an I/O device, communicating with a remote computer, etc. "Clients" are other programs that ask the server to do some specific task. At any time, a server is either performing a task or waiting for a new request. An "event semaphore" is simply an operating system abstraction that allows a client to notify a server that some new request ("event") is pending.
All that the event semaphore does is indicate that a new request has been made. It is up to the client to pass somehow to the server more information about exactly what event has occurred or what work needs to be done. This information could be in the form of files or messages placed on an external data queue.
REXXLIB provides a number of functions for using event semaphores:
Other programming language usually provide some means of creating "libraries" of routines that can be written once and easily reused by many subsequent probrams. OS/2 REXX has the concept of a "macro space", which is really just an obscure name for a code library.
REXX macro spaces consist of the "tokenized" forms of REXX programs which have been saved in a single file. (The "tokenized" form of a REXX program is an internal representation of the program which is designed to make subsequent execution of the code much more efficient, by avoiding translation steps which need to be done only once.)
There are several advantages of using a library to store program code:
Although OS/2 is a multitasking operating system, there is no way in the standard REXX language to take advantage of this by using multiple threads. "Multithreading" means running several activities concurrently within the same program. It is a valuable technique for reducing the total time required to accomplish some overall task by doing several things at the same time. The reason it is possible to save time is that certain operations, especially reading from disk files, actually take very little CPU time, so they can be "overlapped" with computations.
For example, during the initialization of a program you might need to read several parameter and data files, and also compute or otherwise set up various data tables. The reading of each data file could well be moved into a separate thread.
REXXLIB provides the RexxThread function to start a new REXX program on a separate thread. The program can be supplied in one of four different forms:
A REXX program started on a separate thread cannot share variables with the program which initiated it. Any return codes or data must be passed using interprocess communication or files. (See Using semaphores for intertask communication and Passing data to and from external routines for more information.)
Here is a simple example of how this might be used:
pid = dospid() semname = '\sem32\initsem'pid /* get unique semaphore name */ call eventsem_create semname /* create the semaphore */ call rexxthread 'f', 'readini.cmd' /* perform some other initialization while reading initialization data */ ... call eventsem_wait semname /* wait for thread to complete */ call eventsem_close semname /* destroy semaphore */ call varread 'inidata' /* read processed init data */
REXXLIB provides the ShiftState function so that any REXX program can set the key state whichever way it wants. It can also determine the current state, perhaps in order to restore it later. The function can also handle the state of the CapsLock and ScrollLock keys.
/* turn on the NumLock key */
old_state = shiftstate('n', 1)
REXX compound variables are a unique and valuable feature of the language. (See our paper on Advanced REXX Programming for a very thorough discussion.) You can use a compound variable as an "associative array" that can be indexed by arbitrary character strings as well as integral values. But a few important capabilities were left out of the original design of compound variables.
One of these is the ability to iterate over all tails of the compound
variable,
which would be easy to do if "subscripts" were strictly numeric. For
instance, the compound variable catalog_number. might
contain the catalog number of a book, using the title as an index.
The CVTails function can be used to find all tails of a
compound variable. It puts them in an ordinary REXX array which can
be processed sequentially.
call cvtails 'catalog_number', 'list'
do i = 1 to list.0
tail = list.i
say 'The catalog number of' tail 'is' catalog_number.tail'.'
end
There is no way, strictly within the REXX language, to pass an array, compound variable, or any other collection of data to an external routine. Indeed, it is awkward to pass data to even an internal routine, but it is at least possible by "exposing" the variables. This is not allowed when calling external routines.
One alternative that is often used is to pass the data on an external data queue (PUSH or QUEUE the data in the main routine, PULL it in the subroutine). This can be slow and tedious for an array of many elements, if they have to be handled one at a time. Another alternative is to pass the data via a disk file, but this may be even slower.
REXXLIB provides a couple of functions that make it much easier and more efficient to write all of one or more compound variables to a file and later read them back. CVWrite can write all of one compound variable to a file, which can be read back (perhaps into a variable with a different stem) with CVRead. VarWrite can write a whole list of variables, or even all variables in a program, to a file, which can be read back in whole or part with VarRead.
tempfile = dostempname('temp???')
call cvwrite tempfile, 'table'
call tabproc tempfile
call dosdel tempfile
table. is a compound variable holding a
data table (perhaps of two or more dimensions). DosTempName is
a REXXLIB function that generates a temporary file name from a pattern,
and DosDel is a REXXLIB function that deletes the temporary file
when it is no longer needed. Tabproc is the name of the
external routine to be called. It might access the data like this:
parse arg table_file call cvread table_file, data_table
It is often useful, in careful programming, to know whether a given file or directory is located on a CD-ROM (which is read-only). Sometimes it also matters whether the volume in question uses the FAT file system or HPFS, perhaps because of the difference in allowable file names. REXXLIB's DosVolInfo function can be used to obtain this information:
say "The file system on" drive_letter "is" dosvolinfo(drive_letter, 'f')
Many useful OS/2 utilities and services can be implemented by starting a program to operate in the background with the DETACH commanmd. But usually such programs do not have a convenient way of being shut down when they are no longer neeed. REXXLIB provides the DosKillProcess function to do this.
DosKillProcess requires a process ID as argument. You can use the DosPidList function to obtain the process ID. (See Finding active programs and tasks for more information.) Here is how to kill all instances of a particular program:
call dospidlist 'pids', 'names'
call arraysearch 'names', 'list', program_name, 'x'
do i = 1 to list.0
item = list.i /* index in pids. and names. array */
call doskillprocess pids.i
end
It is sometimes necessary in a REXX program to do things differently depending on whether the program is running in a PM session, on one hand, or a full screen or VIO (windowed text mode), on the other. For instance, you might use standard input and output (PULL, SAY) to communicate with a user in a text mode session, or some other technique (e. g. the RxMessageBox function) in a PM session. Since the REXX program may be an external subroutine that could be invoked from any type of session, there needs to be a way to determine the type, and the DosSessionType function provides this:
type = dossessiontype()
if type = 3 /* PM session */
then call rxmessagebox errormsg, 'Error'
else if type = 0 | type = 2 /* text mode */
then say 'An error has occurred:' errormsg
else if type = 4 /* detached session */
then call pmprintf errormsg
"Regular expressions" are a concise way of expressing a specific pattern to be used in search operations. They provide for the ability to match arbitrary characters, only numbers, only letters, white space, etc. Regular expressions can be much more elaborate than patterns which involve only "wildcard" characters, and they can also provide for matching only at the beginning or end of a string.
Several REXXLIB functions are able to use regular expression in search operations:
Regular expressions are not ideal for all searching situations, however. They require that certain special characters (such as ":", "*", "+") be used to specify the pattern instead of standing for themselves. You can use these characters in regular expressions to stand for themselves only by preceding them with an escape character. This is inconvenient to do when the pattern is, for example, requested from a user at run time.
Because of this, all of the searching functions also provide options that offer some flexibility without using regular expressions. For instance, you can specify that alphabetic case is to be considered or ignored, that the pattern must match at the beginning of a line or string, that the pattern must match an entire line or string, or that the operation should succeed when a pattern match does not occur.
As an example of regular expressions, suppose you are looking for
Social Security numbers like 123-45-6789. ":d" is used to match a
single numeric digit, so ":d:d:d-:d:d-:d:d:d:d" would
be the appropriate pattern. Then this code would find all lines in
a file that contain Social Security numbers:
ssn = ":d:d:d-:d:d-:d:d:d:d" count = filesearch(filename, ssn, 'lines') say count 'lines found containing Social Security numbers.'
Good application programs allow user customization by setting of options and preferences. This data has to be saved from one invocation of the program to another. In a game program, one usually wants to be able to save the state of the game so it can be resumed later. This is another example of saving data across invocations. Even more commonly, an application deals with one or more collections of data that represent permanent information which the application uses as a database.
Ultimately, permanent data needs to be saved in a disk file of some sort. A REXX program can always save the data in its own custom format, such as a flat ASCII file. If the data is extensive and highly structured it can be saved in a formal database like DB2/2. IBM's REXXUTIL function package provides functions for reading and writing OS/2 .INI files. However, all of these methods require a moderate to large amount of effort for storing and retrieving data. This is particularly noticeable when the data is naturally structured in lists, tables, etc. as is discussed in our paper on advanced REXX programming.
REXXLIB provides the VarWrite and VarRead functions, to make it possible to write large REXX data structures - or even all the variables in a REXX program - to a disk file and later read it back. For instance,
tempname = dostempname("game???.sav")
call varwrite tempname
call varread tempname
Many problems in debugging REXX programs boil down to being able to see exactly what values have been assigned to REXX variables - to "dump" storage, in effect. This is especially so in REXX, since with compound variables and the INTERPRET instruction, even the names used for variables may not be easy to tell by inspection of the source code. Many problems occur because of problems using compound variables.
REXXLIB provides the VarDump function to handle this. It produces a listing of all or selected variables with their full names and values. This listing can either be displayed on the screen or saved in a file. For instance, here's how to dump selected arrays and tables:
call vardump dumpfile, 'i', 'array1.', 'array2.', 'table1.'
'i' means that only the specified variable
stems are to be dumped. One could use 'e' instead to
request all variables except those specified be dumped.
"Printf" (for the benefit of non-C-programmers) is roughly the C language equivalent of SAY. It is used everywhere in C programming, and often as a debugging aid, since it makes it easy to display the values of variables at key points or simply to verify that some particular part of a program's code has been reached. The same can be done with SAY in REXX, but the problem is that debugging output can destroy careful screen formatting (for a program running in a text mode window). For PM REXX programs, or programs started in the background with the DETACH command, the output from SAY is normally discarded entirely, unless the program uses "console window" features of REXX GUI tools like VisPro/REXX and VX-REXX.
Mike Cowlishaw, the originator of REXX itself, also created a useful tool for debugging under OS/2 called PMPRINTF. It consists of a subroutine ("pmprintf") that can be called from a C program and a simple PM application for displaying the output in a scrollable window. REXXLIB includes a PMPrintf function to allow the same thing in REXX programs. It is especially handy to use in REXX because of the ease of modifying a program, to insert PMPrintf calls, and quickly rerunning it.
To use PMPrintf it is necessary to obtain Cowlishaw's PMPRINTF, which is an IBM "EWS" package. You can download it from our REXX Freeware and Shareware page.
A robust program should carefully test file names supplied by onlines users, configuration files, etc., especially when the supposed name represents a file that is to be created. The file system will not create a file with an invalid name, but the reason for the failure may be difficult to distinguish from other problems, such as the disk being full or the name being already in use for another file or directory. Testing file name validity is surprisingly tricky, particularly since it depends on whether the FAT or HPFS file system is in use.
REXXLIB provides the ValidName function to handle this testing. It even has an option to control whether wildcard characters should be accepted in a particular case.
if \validname(name) then
say '"'name'" is not a valid file name.'
Back to REXXLIB overview
Copyright © 1995 by Quercus Systems, All Rights Reserved
Last updated: October 24, 1995