Project

General

Profile

Process Launching

When a process is launched in 4GL, the developer can choose to either interact with it or let the user interact with the launched process. Developer interaction is done by communicating directly with the process - its input and output pipes can be redirected to a stream or the terminal, letting the developer (and the user) make decisions based on the process's output or control the process by sending data via its input pipe.

In the other mode, only the user can interact with the process and control it (if the process provides such functionality). Here, the developer lets the application give full UI control to the process and, when the user (or the process) decides to end, the application will resume. This functionality allows the user to have access to a shell, a text editor, external mail or other programs, directly from the application.

FWD provides support for both the stream and non-stream process support. This chapter will focus on explaining how FWD converts the 4GL commands which start the process, how the command line looks, how the processes are executed internally and what are the current FWD limitations. For process stream details, please see the Process Streams section of the Streams chapter of this book.

Specifying Program to Run

A child process is launched in two cases:

  • When it is used as an input source or as output target for a procedure (INPUT/OUTPUT/INPUT-OUTPUT THROUGH statements).
  • When it represents a program or script which was launched using the OS-COMMAND, UNIX or other OS-specific statements.

In both cases, a command can be specified to start a certain program. Although some of 4GL statements to open a process appear to be OS specific, FWD will treat all command lines the same, as explained in the following sections.

Command Line Structure

INPUT/OUTPUT/INPUT-OUTPUT THROUGH statements have the following documented format of specifying the program (and its parameters) which supplies data for the Progress procedure or to which you are supplying data from a procedure:

{ program-name | VALUE ( expression ) }
[ argument | VALUE ( expression ) ] ...

UNIX/VMS/OS-COMMAND/BTOS/DOS/OS2 commands have the following documented format to specify the program to be launched:

[ command-token |  VALUE ( expression ) ] ...

In both cases, the command string is represented by a set of elements, separated by spaces:

element1 element2 ...

Each element is converted to a single element of the array which represents the command string and which is passed as the cmdlist parameter to the ProcessOps.launch APIs:

launch([ [ String[] cmdlist, ] boolean silent, boolean wait ])
launch(String[] cmdlist, StreamWrapper sout, StreamWrapper sin)
launch(String[] cmdlist, RemoteStream sout, RemoteStream sin)

The first API is used to launch a shell or an external executable, in silent or no-wait modes, while the last two are used to launch an external executable and link its STDIN, STDOUT and STDERR pipes with the specified (named or unnamed) streams.

Example 1:

unix command1 command2 command3.
input through command4 command5.

Converted code:

ProcessOps.launch(new String[]
{
   "command1",
   "command2",
   "command3" 
}, false, true);

UnnamedStreams.assignIn(StreamFactory.openProcessStream());
ProcessOps.launch(new String[]
{
   "command4",
   "command5" 
}, (RemoteStream) UnnamedStreams.safeInput(), (RemoteStream) null);

Details:

For INPUT/OUTPUT/INPUT-OUTPUT THROUGH statements at least one element should be specified (the name of the program to run). For UNIX/VMS/OS-COMMAND/BTOS/DOS/OS2 statements you may skip the command line: in this case, the default shell will be launched (see Non-Stream Process Launching section for more information).

Command Line Elements

An element in a command line (which specifies the external executable to launch) may be a string constant (quoted or not), a variable or even a complex expression. In each case, the conversion rules will split the command line into distinct elements and pass each one as an element in the cmdList array, while keeping their proper order.

Note that when using string literals, it doesn't matter if they are single (or double) quoted or not - FWD will always convert them to string values and will pass them to the cmdList array.

Example 2:

unix cat > file1.txt

Converted code:

ProcessOps.launch(new String[]
{
   "cat",
   ">",
   "file1.txt" 
}, false, true);

Details:

This example uses plain text literals to specify the launch command. In this case, each element in the command is converted to a string literal, passed to the cmdList array. Note that 4GL uses spaces to split the command into elements.

Example 3:

unix "cat" '> file1.txt'

Converted code:

ProcessOps.launch(new String[]
{
   "cat",
   "> file1.txt" 
}, false, true);

Details:

This example shows that you can use double or single quotes to specify the command. In this case too, a space is used to split the command into elements.

Example 4:

cmd = "cat".
unix value(cmd) value("> " + "file1.txt").

Converted code:

cmd.assign("cat");
ProcessOps.launch(new String[]
{
   (cmd).toStringMessage(),
   (concat("> ", "file1.txt")).toStringMessage()
}, false, true);

Details:

Here, VALUE(expression) clause is used to specify the command. The expression can be of an arbitrary type, and it is converted to a string using the BaseDataType.toStringMessage function.

Example 5:

unix cat ">" value("file1" + ".txt").

Converted code:

ProcessOps.launch(new String[]
{
   "cat",
   ">",
   (concat("file1", ".txt")).toStringMessage()
}, false, true);

Details:

Elements of different types are combined in a single command line.

Command Line Limitations

When specifying a command line, be aware of the following limitations:

  • The second and all subsequent elements of the command line specified in a INPUT/OUTPUT/INPUT-OUTPUT THROUGH statement cannot take the following plain text values (in any letter case), as they are valid options for these statements: ECHO, NO-ECHO, MAP, NO-MAP, UNBUFFERED, NO-CONVERT, CONVERT, PAGED, PAGE-SIZE. You can specify them by enclosing in quotes.

Example 6:

input through echo "Echo" is running.

Converted code:

UnnamedStreams.assignIn(StreamFactory.openProcessStream());
ProcessOps.launch(new String[]
{
   "echo",
   "Echo",
   "is",
   "running" 
}, (RemoteStream) UnnamedStreams.safeInput(), (RemoteStream) null);
  • You cannot use quotes (single or double) in a text literal. If you need to specify quotes then use a quoted string and place target quotes into this string (in compliance with rules of escaping of special characters).

Example 7:

unix echo "\"quoted string\"".

Converted code:

ProcessOps.launch(new String[]
{
   "echo",
   "\"quoted string\"" 
}, false, true);
  • Parsing of a command line ends when a dot followed by white space is encountered, so be aware of it when specifying plain text elements. However something like filename.* is still valid.

Example 8:

unix ls file1.* file2.*.

Converted code:

ProcessOps.launch(new String[]
{
   "ls",
   "file1.*" 
   "file2.*" 
}, false, true);

Process Stream Limitations

Be aware that any child process' STDIN of a process which was executed by an INPUT THROUGH statement and STDOUT and STDERR of a process which was executed by an OUTPUT THROUGH statement are connected to the JVM process' terminal, so:

  • if a process executed by an INPUT THROUGH statement requires some input, it may freeze the terminal;
  • if a process executed by an OUTPUT THROUGH statement outputs some data, it may lead to intermixed output of the process and the Progress application.

Non-Stream Process Launching

Process Launching Statements

4GL Form Description
{ UNIX | DOS | BTOS | OS2 | VMS }
[ SILENT ]
[ command-token |
VALUE ( expression ) ] ...

OS-COMMAND
[ SILENT | NO-WAIT ]
[ NO-CONSOLE ]
[ command-token |
VALUE ( expression ) ] ...
Escapes to the current operating system and executes an operating system command.
NO-CONSOLE option is not supported (it may be Windows-only).
FWD supports the NO-WAIT option for OS-specific statements.

Despite that some statements are declared as OS-specific, they all are converted in the same way and can execute a command under Linux.

4GL parameters:

  • SILENT - Affects the process in the following way:
  • Before processing an operating system command, the terminal is cleared. This option eliminates the clearing.
  • After processing an operating system command, the shell pauses and prompts you to press space bar to continue. This option eliminates this pause.
  • By default STDERR is redirected to STDOUT (which is important for interactive programs). This option eliminates this redirection.
  • This option cannot be used together with the NO-WAIT option.
  • NO-WAIT - causes the Progress application to immediately execute the next 4GL statement without waiting for the operating system command to terminate. This option cannot be used together with the SILENT option.
  • [ command-token | VALUE ( expression ) ] ... specifies the command to be executed. For more details, see the Specifying Program to Run subsection.

Command launching is done with one of the launch methods in the ProcessOps class:

launch()
launch(boolean silent, boolean wait)
launch(String[] cmdlist, boolean silent, boolean wait)

The command is launched as a child process of the current JVM process on the client side, and child process' STDIN, STDOUT and STDERR are connected to the JVM process' terminal. When used in an UNIX/VMS/OS-COMMAND/BTOS/DOS/OS2 statement, launch can receive several parameters:

  • cmdList, to specify the command to be executed
  • silent, a flag which is set to true only when the SILENT option is used.
  • wait, a flag which if set to false only when the NO-WAIT option is used.

Example 1:

unix.

Converted code:

ProcessOps.launch();

Details:

The ProcessOps.launch() is emitted when the command to run is not specified; this function launches the default shell for this system and displays a prompt.

Example 2:

unix silent.

Converted code:

ProcessOps.launch(true, true);

Details:

The ProcessOps.launch(boolean silent, boolean wait) is emitted when the command to run is not specified; this function launches the default shell for this system.

Example 3:

unix echo test.

Converted code:

ProcessOps.launch(new String[]
{
   "echo",
   "test" 
}, false, true);

Details:

The ProcessOps.launch(String[] cmdlist, boolean silent, boolean wait) is emitted when it needs to execute an explicit command, specified by the cmdlist parameter; the flags are similar to those in the previous example.

Process Launching Modes

There are four possible combinations of the SILENT and NO-WAIT options:

  • No options specified (default mode), recommended for interactive applications like shell or vi.
  • SILENT. Recommended for non-interactive applications that produce no output to the terminal, like batch files for performing routine tasks.
  • NO-WAIT. Recommended to be used for non-interactive tasks that can work in background.
    Some launched applications are synchronous and interactive (the user is allowed to interact with the child process' UI). In such cases, the child process' standard I/O must be connected to the terminal. Such programs cannot be run with the NO-WAIT option, because it doesn't work properly in Progress, your terminal gets very "confused". The result is that the child process takes over the terminal and Progress is blocked, no output occurs to the screen except what is being done by the child process.
    FWD allows to run processes in this mode with the following limitations: first, the process shouldn't be interactive (i.e. shouldn't wait for user input), as it may freeze the terminal; second, the process shouldn't perform any output to terminal, because any output performed by it will be intermixed with Progress application output (in a non-deterministic manner), as shown in the next example:

Example 4:

/* press space at the very beginning */
os-command no-wait "sleep 5; echo HELLO;".

def var str as char format "x(20)" init "This is the sentence.".
display str with frame f1.
enable str with frame f1.

wait-for close of current-window.

Converted code:

ProcessOps.launch(new String[]
{
   "sleep 5; echo HELLO;" 
}, false, false);

FrameElement[] elementList0 = new FrameElement[]
{
   new Element(str, f1Frame.widgetStr())
};
f1Frame.display(elementList0);

FrameElement[] elementList1 = new FrameElement[]
{
   new WidgetElement(f1Frame.widgetStr())
};
f1Frame.enable(elementList1);

LogicalTerminal.waitFor(null, new EventList("close", currentWindow()));

Screen output:

str
--------------------
HELLOis the sentence
  • SILENT + NO-WAIT. This mode does not exist in Progress and is not supported on conversion. However, you can specify this mode for the launch function in hand-written code. This mode has the same purpose and peculiarities as the previous mode, with the little changes that the SILENT option provide.

Process Launching Features and Limitations

  • If command to run is not explicitly specified, then the default shell for this system is executed. At this time, the shell is hard coded to sh, but eventually it needs to be moved into the directory or provided as a conversion time hint that is passed to a modified version of launch.
  • If command to run is specified then the actual program to be executed is sh -c <command>, e.g. for this statement:

Example 5:

unix echo test.

Converted code:

ProcessOps.launch(new String[]
{
   "echo",
   "test" 
}, false, true);

Details:

The following program will be executed: sh -c echo test.

  • Note that if user has pressed some keys while synchronous program (without NO-WAIT option) was running, but the program didn't handle it, these keystrokes may be received by Progress application after the program has been finished, as they are cached in the KeyReader's buffer.
  • Progress itself can't know what processes are interactive and which are not. This is true because the fact of whether a process is or is not interactive is something that cannot be inspected, it is implicit in the application (or applications if the child process launches other child processes) being run. In addition, there is no provision in the language for the programmer to provide a hint to Progress about this fact. For this reason, we must conclude that Progress implements the launch of all processes in the same way for each process. For FWD, launching process is "shelling out" the terminal, forking, clearing the terminal (unless SILENT option is specified), and launching the process (see more information about process launching implementation below).
  • On program launch FileSystemOps.getLastError (OS-ERROR) value is forced to 0 which is how Progress handles this value.
  • The NO-WAIT launch in Progress doesn't seem to be really any different from the normal synchronous approach in terms of how the launch itself is done. The only difference seems to be in whether or not Progress waits for the child to exit.
  • Process launching is executed on the client system, while the business logic reside on the FWD server. This includes all logic that accesses and manipulates data, which defines the flow of control for the application. However, when a read or write or file system search or process launch occurs, these operations must be dispatched to the client but made to appear as if they were happening on the server. This allows the logic to remain intact and the minimal amount of processing is pushed to the client. Read the Streams chapter for more information.

Implementation Details

Normally in Java applications external processes are launched using Runtime.exec function, but in this case STDIO of the created child process' will not be connected to a terminal. This means that interactive applications like vi will not properly work when launched by the standard J2SE process launching mechanism. Since Progress allows child processes to be interactive in the current terminal, a different process launching mechanism is needed.

To duplicate this processing, the FWD uses NCURSES (see NCURSES References subsection below for more information about this library) to temporarily "shell out" (see ThinClient.suspend). This means that NCURSES resets the terminal settings to those which were set at NCURSES initialization. At that point, a child process is created (using fork). After fork, the parent process will return and block (unless NO-WAIT was specified) until the child process exits. If NO-WAIT is specified, the parent process resumes. The child process copies the command line parameters and then calls the Linux/UNIX execvp system call. When the child process is done, it exits. The parent process will use ThinClient.resume to re-enable the interactive terminal after it pauses (or just before it returns from ProcessDaemon.launch if NO-WAIT was specified). While the child process runs, all STDIO is directly connected to the parent process' terminal (the child process is directly connected to the user's current terminal).

The process launching API exists in libp2j.so to handle the fork and execvp calls.

The worker for ProcessOps static methods is in the client's ProcessDaemon class. The ProcessDaemon uses features of the ThinClient to properly integrate with the terminal, to suspend/resume it and to handle the actual process launching.

The remote worker methods handle synchronous or asynchronous process launching as documented above. No special proxies or other features are ever returned to the business logic by ProcessOps.launch(String[], boolean, boolean) for these modes. The server-side call invokes the remote worker which launches the child process on the client synchronously or asynchronously and then returns. The server-side call then returns to the business logic.

Note that the ProcessDaemon class has some "cleaner" classes and extra cleanup threads to handle the proper close of the pipes and child process resources as needed.

NCURSES References


© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.