Updated: 11 December 1998 |
OpenVMS Programming Concepts Manual
Previous | Contents | Index |
Using SMG$READ_STRING, you can restrict the user to a certain amount of time in which to respond to a read command. If your application reads data from the terminal using SMG$READ_STRING, you can modify the timeout characteristic by specifying, in the timeout argument, the number of seconds the user has to respond. If the user fails to type a character in the allotted time, the error condition SS$_TIMEOUT (defined in $SSDEF) is returned. The following example restricts the user to 8 seconds in which to respond to a read command:
INTEGER*4 SMG$CREATE_VIRTUAL_KEYBOARD, 2 SMG$READ_STRING, 2 STATUS, 2 VKID, ! Virtual keyboard ID 2 INPUT_SIZE CHARACTER*512 INPUT INCLUDE '($SSDEF)' STATUS = SMG$CREATE_VIRTUAL_KEYBOARD (VKID, 2 'SYS$INPUT') IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) STATUS = SMG$READ_STRING (VKID, ! Keyboard ID 2 INPUT, ! Data read 2 'Prompt> ', 2 512, 2 , 2 8, 2 , 2 INPUT_SIZE) IF (.NOT. STATUS) THEN IF (STATUS .EQ. SS$_TIMEOUT) CALL NO_RESPONSE () ELSE CALL LIB$SIGNAL (%VAL (STATUS)) END IF |
You can cause a QIO read operation to time out after a certain number of seconds by modifying the operation with IO$M_TIMED and by specifying the number of seconds in the P3 argument. A message broadcast to a terminal resets a timer that is set for a timed read operation (regardless of whether the operation was initiated with QIO or SMG).
Note that the timed read operations work on a character-by-character basis. To set a time limit on an input record rather than an input character, you must use the SYS$SETIMR system service. The SYS$SETIMR executes an AST routine at a specified time. The specified time is the input time limit. When the specified time is reached, the AST routine cancels any outstanding I/O on the channel that is assigned to the user's terminal.
9.5.6 Converting Lowercase to Uppercase
You can automatically convert lowercase user input to uppercase by
reading from the terminal with the SMG$READ_STRING routine and by
specifying TRM$M_TM_CVTLOW in the modifiers argument,
as shown in the following example:
INTEGER*4 SMG$CREATE_VIRTUAL_KEYBOARD, 2 SMG$READ_STRING, 2 STATUS, 2 VKID, ! Virtual keyboard ID 2 INPUT_SIZE CHARACTER*512 INPUT INCLUDE '($TRMDEF)' STATUS = SMG$CREATE_VIRTUAL_KEYBOARD (VKID, ! Keyboard ID 2 'SYS$INPUT') IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) STATUS = SMG$READ_STRING (VKID, ! Keyboard ID 2 INPUT, ! Data read 2 'Prompt> ', 2 512, 2 TRM$M_TM_CVTLOW, 2 ,, 2 INPUT_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) |
You can also convert lowercase characters to uppercase with a QIO read
operation modified by IO$M_CVTLOW (defined in $IODEF).
9.5.7 Performing Line Editing and Control Actions
Normally, the user can edit input as explained in the OpenVMS I/O User's Reference Manual. You can inhibit line editing on the read operation by reading from the terminal with SMG$READ_STRING and by specifying TRM$M_TM_NOFILTR in the modifiers argument. The following example shows how you can inhibit line editing:
INTEGER*4 SMG$CREATE_VIRTUAL_KEYBOARD, 2 SMG$READ_STRING, 2 STATUS, 2 VKID, ! Virtual keyboard ID 2 INPUT_SIZE CHARACTER*512 INPUT INCLUDE '($TRMDEF)' STATUS = SMG$CREATE_VIRTUAL_KEYBOARD (VKID, ! Keyboard ID 2 'SYS$INPUT') IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) STATUS = SMG$READ_STRING (VKID, ! Keyboard ID 2 INPUT, ! Data read 2 'Prompt> ', 2 512, 2 TRM$M_TM_NOFILTR, 2 ,, 2 INPUT_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) |
You can also inhibit line editing with a QIO read operation modified by
IO$M_NOFILTR (defined in $IODEF).
9.5.8 Using Broadcasts
You can write, or broadcast, to any interactive terminal by using the SYS$BRKTHRU system service. The following example broadcasts a message to all terminals at which users are currently logged in. Use of SYS$BRKTHRU to write to a terminal allocated to a process other than your own requires the OPER privilege.
INTEGER*4 STATUS, 2 SYS$BRKTHRUW INTEGER*2 B_STATUS (4) INCLUDE '($BRKDEF)' STATUS = SYS$BRKTHRUW (, 2 'Accounting system started',, 2 %VAL (BRK$C_ALLUSERS), 2 B_STATUS,,,,,,) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) |
If the terminal user has taken no action to handle broadcasts, a
broadcast is written to the terminal screen at the current position
(after a carriage return and line feed). If a write operation is in
progress, the broadcast occurs after the write ends. If a read
operation is in progress, the broadcast occurs immediately; after the
broadcast, any echoed user input to the aborted read operation is
written to the screen (same effect as pressing Ctrl/R).
9.5.8.2 How to Create Alternate Broadcast Handlers
You can handle broadcasts to the terminal on which your program is running with SMG$SET_BROADCAST_TRAPPING. This routine uses the AST mechanism to transfer control to a subprogram of your choice each time a broadcast message is sent to the terminal; when the subprogram completes, control returns to the point in your mainline code where it was interrupted.
The SMG$SET_BROADCAST_TRAPPING routine is not an SMG$ input routine. Before invoking SMG$SET_BROADCAST_TRAPPING, you must invoke SMG$CREATE_PASTEBOARD to associate a pasteboard with the terminal. SMG$CREATE_PASTEBOARD returns a pasteboard identification number; pass that number to SMG$SET_BROADCAST_TRAPPING to identify the terminal in question. Read the contents of the broadcast with SMG$GET_BROADCAST_MESSAGE.
Example 9-15 demonstrates how you might trap a broadcast and write it at the bottom of the screen. For more information about the use of SMG$ pasteboards and virtual displays, see Section 9.4.
Example 9-15 Trapping Broadcast Messages |
---|
. . . INTEGER*4 STATUS, 2 PBID, ! Pasteboard ID 2 VDID, ! Virtual display ID 2 SMG$CREATE_PASTEBOARD, 2 SMG$SET_BROADCAST_TRAPPING 2 SMG$PASTE_VIRTUAL_DISPLAY COMMON /ID/ PBID, 2 VDID INTEGER*2 B_STATUS (4) INCLUDE '($SMGDEF)' INCLUDE '($BRKDEF)' EXTERNAL BRKTHRU_ROUTINE STATUS = SMG$CREATE_PASTEBOARD (PBID) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) STATUS = SMG$CREATE_VIRTUAL_DISPLAY (3, ! Height 2 80, ! Width 2 VDID,, ! Display ID 2 SMG$M_REVERSE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) STATUS = SMG$SET_BROADCAST_TRAPPING (PBID, ! Pasteboard ID 2 BRKTHRU_ROUTINE) ! AST IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) . . . |
SUBROUTINE BRKTHRU_ROUTINE () INTEGER*4 STATUS, 2 PBID, ! Pasteboard ID 2 VDID, ! Virtual display ID 2 SMG$GET_BROADCAST_MESSAGE, 2 SMG$PUT_CHARS, 2 SMG$PASTE_VIRTUAL_DISPLAY COMMON /ID/ PBID, 2 VDID CHARACTER*240 MESSAGE INTEGER*2 MESSAGE_SIZE ! Read the message STATUS = SMG$GET_BROADCAST_MESSAGE (PBID, 2 MESSAGE, 2 MESSAGE_SIZE) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Write the message to the virtual display STATUS = SMG$PUT_CHARS (VDID, 2 MESSAGE (1:MESSAGE_SIZE), 2 1, ! Line 2 1) ! Column IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) ! Make the display visble by pasting it to the pasteboard STATUS = SMG$PASTE_VIRTUAL_DISPLAY (VDID, 2 PBID, 2 22, ! Row 2 1) ! Column END |
This chapter describes file operations that support file input/output (I/O) and file I/O instructions of the operating system's high-level languages. This chapter contains the following sections:
Section 10.1 describes file attributes.
Section 10.2 describes strategies to access files.
Section 10.3 describes protection and access of files.
Section 10.4 describes file mapping.
Section 10.5 describes how to open and update a sequential file.
Section 10.6 describes using the Fortran user-open routines.
I/O statements transfer data between records in files and variables in your program. The I/O statement determines the operation to be performed; the I/O control list specifies the file, record, and format attributes; and the I/O list contains the variables to be acted upon.
Some confusion might arise between records in a file and record variables. Where this chapter refers to a record variable, the term record variable is used; otherwise, record refers to a record in a file. |
Before writing a program that accesses a data file, you must know the attributes of the file and the order of the data. To determine this information, see your language-specific programming manual.
File attributes (organization, record structure, and so on) determine how data is stored and accessed. Typically, the attributes are specified by keywords when you open the data file.
Ordering of the data within a file is not important mechanically.
However, if you attempt to read data without knowing how it is ordered
within the file, you are likely to read the wrong data; if you attempt
to write data without knowing how it is ordered within the file, you
are likely to corrupt existing data.
10.1.1 Specifying File Attributes
Large sets of attributes can be specified using the File Definition Language utility (FDL). All of the file attributes can be specified using OpenVMS RMS in a user-open routine (see Section 10.6). Typically, you need only programming language file specifiers. Use FDL only when language specifiers are unavailable.
Refer to the appropriate programming language reference manual for information about the use of language specifiers.
For complete information about how to use FDL, see the OpenVMS Record Management Utilities Reference Manual.
10.1.2 Using Extended File Specifications (Alpha Only)
On Alpha systems running OpenVMS V7.2, extended file specifications
supports disk file specifications of greater length, a larger character
set, and deeper directories than was supported on previous versions of
Alpha OpenVMS. For information about extended file specifications, see
the OpenVMS Guide to Extended File Specifications and the Guide to OpenVMS File Applications for a description of these
features.
10.2 File Access Strategies
When determining the file attributes and order of your data file, consider how you plan to access that data. File access strategies fall into the following categories:
Files are owned by the process that creates them and receive the
default protection of the creating process. To create a file with
ownership and protection other than the default, use the File
Definition Language (FDL) attributes OWNER and PROTECTION in the file.
10.3.1 Read-Only Access
By default, the user of your program must have write access to a file
in order for your program to open that file. However, if you specify
use of the Fortran READONLY specifier when opening the file, the user
needs only read access to the file in order to open it. The READONLY
specifier does not set the protection on a file. The user cannot write
to a file opened with the READONLY specifier.
10.3.2 Shared Access
The Fortran specifier READONLY and the SHARED specifier allow multiple processes to open the same file simultaneously, provided that each process uses one of these specifiers when opening the file. The READONLY specifier allows the process read access to the file; the SHARED specifier allows other processes read and write access to the file. If a process opens the file without specifying READONLY or SHARED, no other process can open that file even by specifying READONLY or SHARED.
In the following Fortran segment, if the read operation indicates that the record is locked, the read operation is repeated. You should not attempt to read a locked record without providing a delay (in this example, the call to ERRSNS) to allow the other process time to complete its operation and unlock the record.
! Status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' ! Logical unit number INTEGER LUN /1/ ! Record variables INTEGER LEN CHARACTER*80 RECORD . . . READ (UNIT = LUN, 2 FMT = '(Q,A)' 2 IOSTAT = IOSTAT) LEN, RECORD (1:LEN) IF (IOSTAT .NE. IO_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .EQ. FOR$_SPERECLOC) THEN DO WHILE (STATUS .EQ. FOR$_SPERECLOC) READ (UNIT = LUN, 2 FMT = '(Q,A)' 2 IOSTAT = IOSTAT) LEN, RECORD(1:LEN) IF (IOSTAT .NE. IO_OK) THEN CALL ERRSNS (,,,,STATUS) IF (STATUS .NE. FOR$_SPERECLOC) THEN CALL LIB$SIGNAL(%VAL(STATUS)) END IF END IF END DO ELSE CALL LIB$SIGNAL (%VAL(STATUS)) END IF END IF . . . |
In Fortran, each time you access a record in a shared file, that record
is automatically locked until you perform another I/O operation on the
same logical unit, or until you explicitly unlock the record using the
UNLOCK statement. If you plan to modify a record, you should do so
before unlocking it; otherwise, you should unlock the record as soon as
possible.
10.4 File Access and Mapping
To copy an entire data file from the disk to program variables and back
again, either use language I/O statements to read and write the data or
use the Create and Map Section (SYS$CRMPSC) system service to map the
data. Often times, mapping the file is faster than reading it. However,
a mapped file usually uses more virtual memory than one that is read
using language I/O statements. Using I/O statements, you have to store
only the data that you have entered. Using SYS$CRMPSC, you have to
initialize the database and store the entire structure in virtual
memory including the parts that do not yet contain data.
10.4.1 Using SYS$CRMPSC
Mapping a file means associating each byte of the file with a byte of program storage. You access data in a mapped file by referencing the program storage; your program does not use I/O statements.
Files created using OpenVMS RMS typically contain control information. Unless you are familiar with the structure of these files, do not attempt to map one. The best practice is to map only those files that have been created as the result of mapping. |
To map a file, perform the following operations:
PSECT_ATTR = name, PAGE |
PSECT_ATTR = name, solitary |
Do not initialize variables in a common block that you plan to map; the
initial values will be lost when SYS$CRMPSC maps the common block.
10.4.1.1 Mapping a File
The format for SYS$CRMPSC is as follows:
SYS$CRMPSC [inadr] ,[retadr] ,[acmode] ,[flags]
,[gsdnam] ,[ident] ,[relpag] ,[chan] ,[pagcnt] ,[vbn] ,[prot] ,[pfc]
For a complete description of the SYS$CRMPSC system service, see the OpenVMS System Services Reference Manual.
Starting and Ending Addresses of the Mapped Section
On VAX systems, specify the location of the first variable in the common block as the value of the first array element of the array passed by the inadr argument. Specify the location of the last variable in the common block as the value of the second array element.
On Alpha systems, specify the location of the first variable in the common block as the value of the first array element of the array passed by the inadr argument; the second array element must be the address of the last variable in the common block, which is derived by performing a logical OR with the value of the size of a memory page minus 1. The size of the memory page can be retrieved by a call to the SYS$GETSYI system service.
If the first variable in the common block is an array or string, the first variable in the common block is the first element of that array or string. If the last variable in the common block is an array or string, the last variable in the common block is the last element in that array or string.
Returning the Location of the Mapped Section
On VAX systems, SYS$CRMPSC returns the location of the first and last elements mapped in the retadr argument. The value returned as the starting virtual address should be the same as the starting address passed to the inadr argument. The value returned as the ending virtual address should be equal to or slightly more than (within 512 bytes, or 1 block) the value of the ending virtual address passed to the inadr argument.
On Alpha systems, SYS$CRMPSC returns the location of the first and last elements mapped in the retadr argument. The value returned as the starting virtual address should be the same as the starting address passed to the inadr argument. The value returned as the ending virtual address should be equal to or slightly less than (within a single page size) the value of the ending virtual address passed to the inadr argument.
If the first element is in error, you probably forgot to page-align the common block containing the mapped data.
If the second element is in error, you were probably creating a new data file and forgot to specify the size of the file in your program (see Section 10.4.1.3).
Specify SEC$M_WRT for the flags to indicate that the section is writable. If the file is new, also specify SEC$M_DZRO to indicate that the section should be initialized to zero.
You must use a user-open routine to get the channel number (see Section 10.4.1.2). Pass the channel number to the chan argument.
On VAX systems, Example 10-1 maps a data file consisting of one longword and three real arrays to the INC_DATA common block. The options file INCOME.OPT page-aligns the INC_DATA common block.
If SYS$CRMPSC returns a status of SS$_IVSECFLG and you have correctly specified the flags in the mask argument, check to see if you are passing a channel number of 0.
Example 10-1 Mapping a Data File to the Common Block on a VAX System |
---|
!INCOME.OPT PSECT_ATTR = INC_DATA, PAGE |
! Declare variables to hold statistics REAL PERSONS_HOUSE (2048), 2 ADULTS_HOUSE (2048), 2 INCOME_HOUSE (2048) INTEGER TOTAL_HOUSES ! Declare section information ! Data area COMMON /INC_DATA/ PERSONS_HOUSE, 2 ADULTS_HOUSE, 2 INCOME_HOUSE, 2 TOTAL_HOUSES ! Addresses INTEGER ADDR(2), 2 RET_ADDR(2) ! Section length INTEGER SEC_LEN ! Channel INTEGER*2 CHAN, 2 GARBAGE COMMON /CHANNEL/ CHAN, 2 GARBAGE ! Mask values INTEGER MASK INCLUDE '($SECDEF)' ! User-open routines INTEGER UFO_OPEN, 2 UFO_CREATE EXTERNAL UFO_OPEN, 2 UFO_CREATE ! Declare logical unit number INTEGER STATS_LUN ! Declare status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' EXTERNAL INCOME_BADMAP ! Declare logical for INQUIRE statement LOGICAL EXIST ! Declare subprograms invoked as functions INTEGER LIB$GET_LUN, 2 SYS$CRMPSC, 2 SYS$DELTVA, 2 SYS$DASSGN ! Get logical unit number for STATS.SAV STATUS = LIB$GET_LUN (STATS_LUN) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) INQUIRE (FILE = 'STATS.SAV', 2 EXIST = EXIST) IF (EXIST) THEN OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='OLD', 2 USEROPEN = UFO_OPEN) MASK = SEC$M_WRT ELSE ! If STATS.SAV does not exist, create new database MASK = SEC$M_WRT .OR. SEC$M_DZRO SEC_LEN = ! (address of last - address of first + size of last + 511)/512 2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 ) OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='NEW', 2 INITIALSIZE = SEC_LEN, 2 USEROPEN = UFO_CREATE) END IF ! Free logical unit number and map section CLOSE (STATS_LUN) ! ******** ! MAP DATA ! ******** ! Specify first and last address of section ADDR(1) = %LOC(PERSONS_HOUSE(1)) ADDR(2) = %LOC(TOTAL_HOUSES) ! Map the section STATUS = SYS$CRMPSC (ADDR, 2 RET_ADDR, 2 , 2 %VAL(MASK), 2 ,,, 2 %VAL(CHAN), 2 ,,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) ! Check for correct mapping IF (ADDR(1) .NE. RET_ADDR (1)) 2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP))) . . . ! Reference data using the ! data structures listed ! in the common block . . . ! Close and update STATS.SAV STATUS = SYS$DELTVA (RET_ADDR,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) STATUS = SYS$DASSGN (%VAL(CHAN)) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) END |
Example 10-2 shows the code for performing the same functions as Example 10-1 but in an Alpha system's environment.
Example 10-2 Mapping a Data File to the Common Block on an Alpha System |
---|
!INCOME.OPT PSECT_ATTR = INC_DATA, SOLITARY, SHR, WRT |
! Declare variables to hold statistics REAL PERSONS_HOUSE (2048), 2 ADULTS_HOUSE (2048), 2 INCOME_HOUSE (2048) INTEGER TOTAL_HOUSES, STATUS ! Declare section information ! Data area COMMON /INC_DATA/ PERSONS_HOUSE, 2 ADULTS_HOUSE, 2 INCOME_HOUSE, 2 TOTAL_HOUSES ! Addresses INTEGER ADDR(2), 2 RET_ADDR(2) ! Section length INTEGER SEC_LEN ! Channel INTEGER*2 CHAN, 2 GARBAGE COMMON /CHANNEL/ CHAN, 2 GARBAGE ! Mask values INTEGER MASK INCLUDE '($SECDEF)' ! User-open routines INTEGER UFO_OPEN, 2 UFO_CREATE EXTERNAL UFO_OPEN, 2 UFO_CREATE ! Declare logical unit number INTEGER STATS_LUN ! Declare status variables and values INTEGER STATUS, 2 IOSTAT, 2 IO_OK PARAMETER (IO_OK = 0) INCLUDE '($FORDEF)' EXTERNAL INCOME_BADMAP ! Declare logical for INQUIRE statement LOGICAL EXIST ! Declare subprograms invoked as functions INTEGER LIB$GET_LUN, 2 SYS$CRMPSC, 2 SYS$DELTVA, 2 SYS$DASSGN ! Get logical unit number for STATS.SAV STATUS = LIB$GET_LUN (STATS_LUN) IF (.NOT. STATUS) CALL LIB$SIGNAL (%VAL (STATUS)) INQUIRE (FILE = 'STATS.SAV', 2 EXIST = EXIST) IF (EXIST) THEN OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='OLD', 2 USEROPEN = UFO_OPEN) MASK = SEC$M_WRT ELSE ! If STATS.SAV does not exist, create new database MASK = SEC$M_WRT .OR. SEC$M_DZRO SEC_LEN = ! (address of last - address of first + size of last + 511)/512 2 ( (%LOC(TOTAL_HOUSES) - %LOC(PERSONS_HOUSE(1)) + 4 + 511)/512 ) OPEN (UNIT=STATS_LUN, 2 FILE='STATS.SAV', 2 STATUS='NEW', 2 INITIALSIZE = SEC_LEN, 2 USEROPEN = UFO_CREATE) END IF ! Free logical unit number and map section CLOSE (STATS_LUN) ! ******** ! MAP DATA ! ******** STATUS = LIB$GETSYI(SYI$_PAGE_SIZE, PAGE_MAX,,,,) IF (.NOT. STATUS) CALL LIB$STOP (%VAL (STATUS)) ! Specify first and last address of section ADDR(1) = %LOC(PERSONS_HOUSE(1)) ! Section will always be smaller than page_max bytes ADDR(2) = ADDR(1) + PAGE_MAX -1 ! Map the section STATUS = SYS$CRMPSC (ADDR, 2 RET_ADDR, 2 , 2 %VAL(MASK), 2 ,,, 2 %VAL(CHAN), 2 ,,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) ! Check for correct mapping IF (ADDR(1) .NE. RET_ADDR (1)) 2 CALL LIB$SIGNAL (%VAL (%LOC(INCOME_BADMAP))) . . . ! Reference data using the ! data structures listed ! in the common block . . . ! Close and update STATS.SAV STATUS = SYS$DELTVA (RET_ADDR,,) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) STATUS = SYS$DASSGN (%VAL(CHAN)) IF (.NOT. STATUS) CALL LIB$SIGNAL(%VAL(STATUS)) END |
Previous | Next | Contents | Index |
Copyright © Compaq Computer Corporation 1998. All rights reserved. Legal |
5841PRO_027.HTML
|