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 toSTDOUT
(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 theSILENT
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 executedsilent
, a flag which is set to true only when theSILENT
option is used.wait
, a flag which if set to false only when theNO-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 theNO-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 thelaunch
function in hand-written code. This mode has the same purpose and peculiarities as the previous mode, with the little changes that theSILENT
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 oflaunch
. - 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 theKeyReader
'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¶
- http://www.faqs.org/docs/Linux-HOWTO/NCURSES-Programming-HOWTO.html
- http://invisible-island.net/ncurses/ncurses.faq.html
- http://www.tldp.org/HOWTO/Text-Terminal-HOWTO.html (especially the bit about termcap/terminfo and the part on psuedo-terminals)
- http://dickey.his.com/ncurses/ncurses-intro.html
© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.