Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How can Informix 4GL interact with Unix?

Calling

How can Informix 4GL interact with Unix?

by  olded  Posted    (Edited  )
CALLING "C" FUNCTIONS FROM INFORMIX 4GL

Informix 4GL, both Rapid Development System (RDS) and Compiled 4GL, provide a powerful SQL-based environment for developing applications under Unix. However, their ability to interact with the operating system is limited. Operations such as determining file existence/size and
reading/writing ascii files are difficult if not impossible without hooks to the operating system.

This FAQ discusses "C" functions that provide the ability to return the output of valid Unix commands to Informix 4GL. It also includes functions to read and write disk files. You don't have to be a "C" programmer to use
these functions - just invoke them correctly from 4GL.

I also discuss a method for linking these "C" functions to make a new Informix RDS run-time.


ONE-LINE RETURN, rx_shell()

The rx_shell() function takes a command-to-be-executed string and returns the output. Below is an example which returns the value of the HOME shell variable:

main
define ret_str char(20),
com_str char(20)

let com_str = "echo $HOME"
#another form - call rx_shell(com_str) returning ret_str
let ret_str = rx_shell(com_str)
#display HOME variable
display ret_str
end main

Notice the Informix character string variable definitions; make sure the variable, ret_str, is large enough to hold rx_shell()'s largest return value or it will be truncated.

RETURNING FILE SIZE

Rx_shell() allows basic Unix commands to be piped together. The following example returns the size of file ascii.file:

let ret_str = rx_shell("ls -l ascii.file|awk '{print $5}'")

Assuming a field delimiter of spaces, the fifth field of a long listing is the size of the file in bytes. Pipe a long listing of the file to awk and print the fifth field which returns the size of the file.

TESTING FOR FILE EXISTENCE

Rx_shell() allows basic Unix commands to be executed as a list. The following example executes the Unix test command to see if the file,ascii.file, is readable:

let ret_str = rx_shell("test -r asii.file; echo $?")

Every command executed by the unix shell, sets the exit code ($?) to 0 for successful and non-zero if not.

Here I mail two lines to user eds:

main
define xx smallint,
sstd char(80)

whenever error continue

let xx = rx_shopenw("mailx eds", 0) #open a valid unix command
let xx = rx_shwrite("send line 1", 0) # write to the pipe
let xx = rx_shwrite("send line 2", 0)
call rx_shclose(0) #be sure to close the pipe

end main

RX_SHELL DISCUSSION

Informix 4GL passes parameters to "C" functions and receives return values the stack; the act of pushing-to
and poping-from the stack also converts between Informix data types and "C" data types. The stack is a last-in, first-out (LIFO) queue so variables are always popped in reverse order. The following Informix library functions control the integer and character string data types:

Popping Function Pushing Function Argument Types
popint(&i) retint(i) int i;
popquote(str,len) retquote(str) char *str;

Listing 1 is the source code for rx_shell; since the command string passed to rx_shell() is on the stack, only the number of parameters on the stack, n, is passed to the function. The command string is popped off the stack using the popquote function call. Make sure the command string
length is less than the size of the buffer.

I execute the command string for reading by opening a pipe to the shell using the popen() library function. Next, read the input stream with fread(). After closing the pipe, push the return string onto the stack with retquote() function call. Finally, return
the number of parameters pushed onto the stack.


MULTIPLE LINE READ

When a Unix command returns more than one line, such as reading an ascii file using the cat command, open a piped command, read the pipe and then close it. Below is an example Informix 4GL program:

main
define xx smallint,
sstd char(80)

whenever error continue

let xx = rx_shopen("cat eds.file", 0) #open a valid unix command for read
let xx = rx_shopenw("cat >> new.file", 1) #open a valid unix command for write
while true
let sstd = rx_shread(0)
if sstd is null
then #stop when you've reached the end of the pipe
exit while
end if
display sstd
let xx = rx_shwrite(sstd, 1) # write to the new file
end while
call rx_shclose(0) #be sure to close the pipe
call rx_shclose(1) #be sure to close the pipe

end main

The above example uses file handels. In the example above file handel 0 is for reading and 1 is for writing. The code supports 18 handles, but you can increase #define file_no 18 if you wish.

MULTIPLE LINE READ DISCUSSION

Essentially, break rx_shell() into an open function, rx_shopen(), a read function, rx_shread(), and a close function, rx_shclose(). Listing 2 is the source for the above mentioned functions. Defining the FILE structure locally global, allows all functions defined in this source module to act on the pointer-to-FILE, *pipe_in.

The FILE structure is an array, so make sure you open and close the correct one.

The rx_shread() function returns a line of input via a read from the pipe or a blank space at EOF. Since the "C" library fgets() function retains a new-line when it reads characters from the stream, replace the new-line with a null.

MULTIPLE LINE WRITE

Writing to the pipe is also possible. The following Informix 4GL program creates an ascii file with two lines:

main
define xx smallint #informix data type -32767 to +32767

let xx = rx_shopenw("cat > ascii.file", 0)
let xx = rx_shwrite("First line of ascii.file", 0)
let xx = rx_shwrite("Second line of ascii.file", 0)
call rx_close(0)
end main

Here I send a two-line message to user eds:

main
define xx smallint,
sstd char(80)

whenever error continue

let xx = rx_shopenw("mailx eds", 0) #open a valid unix command
let xx = rx_shwrite("send line 1", 0) # write to the pipe
let xx = rx_shwrite("send line 2", 0)
call rx_shclose(0) #be sure to close the pipe

end main

MULTIPLE LINE WRITE DISCUSSION

The only difference between the two open functions, rx_shopen() and rx_shopenw(), is one stream is opened for reading and the other for writing, repectively. The two functions could be one if the fopen() mode parameter were passed on the stack. I chose to limit stack activity
by creating two separate functions.

The rx_shwrite() function pops the string to be written from the stack. When Informix 4GL pushes a string to the stack, any trailing spaces are also pushed. The ldchar() function, an Informix supplied "C" function, trims any trailing spaces. Append a new-line using the strcat() function. Write the string to the stream using the fputs() call. If the write was successful return a zero else a negative one.

CREATING A NEW INFORMIX RUN-TIME

In order to use the "C" functions with RDS, the Informix run-time must know of the existence of the functions. Informix provides a shell script, cfglgo, functions to create a new run-time. Assuming the
Informix binaries are installed in /usr/informix,
copy /usr/informix/etc/fgiusr.c to your working directory. Also, copy the run-time, /usr/informix/bin/fglgo, to your working directory.

MODIFYING FGIUSR.C

First, edit fgiusr.c, declaring each "C" function:

int rx_shopen();
int rx_shopenw();
int rx_shread();
int rx_shwrite();
int rx_shclose();
int rx_shell();


Next, edit the usrcfuncs[] structure located in fgiusr.c. The first initializer is a character pointer, the second is a function pointer, and the third is the number of arguments expected by the function.
Make sure the line of zeros remains at the end of the structure declaration:

cfunc_t usrcfuncs[] =
{
"rx_shopen", rx_shopen,2,
"rx_shopenw", rx_shopenw,2,
"rx_shread", rx_shread,1,
"rx_shwrite", rx_shwrite,2,
"rx_shclose", rx_shclose,2,
"rx_shell", rx_shell,1,
0, 0, 0
};

Assuming a new run-time named nfglgo, execute the following
from the command line:

cfglgo fgiusr.c rxsh.c rxshell.c -o nfglgo

Finally, copy the new run-time, nfglgo, back to /usr/informix/bin.

In order to link the "C" functions to the Informix debugger, a similar
shell script, cfgldb, creates a new debugger run-time. For more
information, see the Informix 4GL Reference Manual, Volume 1.

CONCLUSION

An Informix developer now has greater access to Unix using these "C" functions. Use them only when necessary, since:

1) Another shell is spawned when a call to the stream functions, such as fopen(), is made consuming system resources.

2) Linking in "C" modules increases the size of the Informix run-time creating a larger executable.

REFERENCES

Informix 4GL Reference Manual, Volume 1, Informix Software, Inc. 1990.

< Listings follow>

/* Listing 1. rxshell.c
*
* rx_shell - Call from Informix 4GL
* 1) pop one argument from the stack
* 2) execute the pipe
* 3) read one line from pipe
* 4) close the pipe
* 5) push the line read on the stack
*/
#include <stdio.h>

int rx_shell(n)
int n; /* number of arguments passed */
{
FILE *pipe_in;
char in_buf[BUFSIZ];
char sh_cmd[128];
int cnt;

/* pop the character string from the stack */
popquote(sh_cmd, sizeof(sh_cmd));

if((pipe_in = popen(sh_cmd,"r")) == NULL)
{
fprintf(stderr,"popen failed\n");
in_buf[0] = '\0';
}
else
{
cnt = fread(in_buf, 1, BUFSIZ, pipe_in);
if(cnt)
{
if(in_buf[cnt-1] == '\n')
in_buf[cnt-1] = '\0';
}
}

pclose(pipe_in); /* close the pipe */

retquote(in_buf); /* push character string on the stack */

return(1); /* return one argument */
}


/*******************************************************************************
* Listing 2. rxsh.c
* Run a shell command returning standard output.
******************************************************************************/

#include <stdio.h>
#define file_no 18


FILE *f_ptr[file_no];


int rx_shopen(n)
int n;
{
char sh_cmd[BUFSIZ];
int ret;
int file_ptr;

popint(&file_ptr);
popquote(sh_cmd, sizeof(sh_cmd));

if(file_ptr > file_no)
{
fprintf(stderr,"file handle number is too large \n");
ret = -1;
}
if((f_ptr[file_ptr] = popen(sh_cmd,"r")) == NULL )
{
fprintf(stderr,"popen failed\n");
ret = -1;
}
else
ret = 0;

retint(ret); /* TRUE (-1) if failed */

return(1); /* return one argument */
}

int rx_shopenw(n)
int n;
{
char sh_cmd[BUFSIZ];
int ret;
int file_ptr;

popint(&file_ptr);
popquote(sh_cmd, sizeof(sh_cmd));
ldchar(sh_cmd,BUFSIZ-1,sh_cmd);

if((f_ptr[file_ptr] = popen(sh_cmd,"w")) == NULL)
{
fprintf(stderr,"popen failed\n");
ret = -1;
}
else
ret = 0;

retint(ret); /* TRUE (-1) if failed */
return(1); /* return one argument */
}


int rx_shread(n)
int n;
{
char in_buf[BUFSIZ];
int end;
int file_ptr;

popint(&file_ptr);
if(f_ptr[file_ptr] == NULL)
*in_buf = '\0';
else
if(fgets(in_buf,BUFSIZ,f_ptr[file_ptr]))
{
end = strlen(in_buf) - 1;
if (end != 0)
{
if (in_buf[end] == '\n')
in_buf[end] = '\0';
}
else
{
/* make into blank line, not null */
in_buf[0] = ' ';
in_buf[1] = '\0';
}
}
else
in_buf[0] = '\0';


retquote(in_buf);

return(1); /* return one argument */
}


int rx_shclose(n)
int n;
{
int file_ptr;

popint(&file_ptr);
if (f_ptr[file_ptr] != NULL)
pclose(f_ptr[file_ptr]);

return(0);
}

int rx_shwrite(n)
int n;
{
char sh_cmd[BUFSIZ];
int ret;
int file_ptr;

popint(&file_ptr);
popquote(sh_cmd, sizeof(sh_cmd));
ldchar(sh_cmd,BUFSIZ-1,sh_cmd);
strcat(sh_cmd,"\n");

if(fputs(sh_cmd,f_ptr[file_ptr]) == EOF)
{
fprintf(stderr,"Error writing to pipe\n");
ret = -1;
}
else
ret = 0;

retint(ret); /* TRUE (-1) if failed */
return(1);
}
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top