341 lines
12 KiB
Prolog
341 lines
12 KiB
Prolog
/* $Id$
|
|
|
|
Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemak@uva.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 2008-2009, University of Amsterdam
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
As a special exception, if you link this library with other files,
|
|
compiled with a Free Software compiler, to produce an executable, this
|
|
library does not by itself cause the resulting executable to be covered
|
|
by the GNU General Public License. This exception does not however
|
|
invalidate any other reasons why the executable file might be covered by
|
|
the GNU General Public License.
|
|
*/
|
|
|
|
:- module(process,
|
|
[ process_create/3, % +Exe, +Args, +Options
|
|
process_wait/2, % +PID, -Status
|
|
process_wait/3, % +PID, -Status, +Options
|
|
process_id/1, % -PID
|
|
process_id/2, % +Process, -PID
|
|
is_process/1, % +PID
|
|
process_release/1, % +PID
|
|
process_kill/1, % +PID
|
|
process_kill/2 % +PID, -Signal
|
|
]).
|
|
:- use_module(library(shlib)).
|
|
:- use_module(library(lists)).
|
|
:- use_module(library(option)).
|
|
|
|
:- use_foreign_library(foreign(process)).
|
|
|
|
/** <module> Create processes and redirect I/O
|
|
|
|
The module library(process) implements interaction with child processes
|
|
and unifies older interfaces such as shell/[1,2], open(pipe(command),
|
|
...) etc. This library is modelled after SICStus 4.
|
|
|
|
The main interface is formed by process_create/3. If the process id is
|
|
requested the process must be waited for using process_wait/2. Otherwise
|
|
the process resources are reclaimed automatically.
|
|
|
|
In addition to the predicates, this module defines a file search path
|
|
(see user:file_search_path/2 and absolute_file_name/3) named =path= that
|
|
locates files on the system's search path for executables. E.g. the
|
|
following finds the executable for =ls=:
|
|
|
|
==
|
|
?- absolute_file_name(path(ls), Path, [access(execute)]).
|
|
==
|
|
|
|
*|Incompatibilities and current limitations|*
|
|
|
|
* Where SICStus distinguishes between an internal process id and
|
|
the OS process id, this implements does not make this distinction.
|
|
This implies that is_process/1 is incomplete and unreliable.
|
|
|
|
* SICStus only supports ISO 8859-1 (latin-1). This implementation
|
|
supports arbitrary OS multibyte interaction using the default
|
|
locale.
|
|
|
|
* It is unclear what the detached(true) option is supposed to do. Disable
|
|
signals in the child? Use setsid() to detach from the session? The
|
|
current implementation uses setsid()
|
|
|
|
* An extra option env([Name=Value, ...]) is added to
|
|
process_create/3.
|
|
|
|
@tbd Implement detached option in process_create/3
|
|
@compat SICStus 4
|
|
*/
|
|
|
|
|
|
/*******************************
|
|
* PATH HANDLING *
|
|
*******************************/
|
|
|
|
:- multifile
|
|
user:file_search_path/2.
|
|
:- dynamic
|
|
user:file_search_path/2.
|
|
|
|
user:file_search_path(path, Dir) :-
|
|
getenv('PATH', Path),
|
|
( current_prolog_flag(windows, true)
|
|
-> atomic_list_concat(Dirs, (;), Path)
|
|
; atomic_list_concat(Dirs, :, Path)
|
|
),
|
|
member(Dir, Dirs).
|
|
|
|
|
|
%% process_create(+Exe, +Args:list, +Options) is det.
|
|
%
|
|
% Create a new process running the file Exe and using arguments
|
|
% from the given list. Exe is a file specification as handed to
|
|
% absolute_file_name/3. Typically one use the =path= file alias to
|
|
% specify an executable file on the current PATH. Args is a list
|
|
% of arguments that are handed to the new process. On Unix
|
|
% systems, each element in the list becomes a seperate argument in
|
|
% the new process. In Windows, the arguments are simply
|
|
% concatenated to form the commandline. Each argument itself is
|
|
% either a primitive or a list of primitives. A primitive is
|
|
% either atomic or a term file(Spec). Using file(Spec), the system
|
|
% inserts a filename using the OS filename conventions which is
|
|
% properly quoted if needed.
|
|
%
|
|
% Options:
|
|
%
|
|
% * stdin(Spec)
|
|
% * stdout(Spec)
|
|
% * stderr(Spec)
|
|
% Bind the standard streams of the new process. Spec is one of
|
|
% the terms below. If pipe(Pipe) is used, the Prolog stream is
|
|
% a stream in text-mode using the encoding of the default
|
|
% locale. The encoding can be changed using set_stream/2.
|
|
%
|
|
% * std
|
|
% Just share with the Prolog I/O streams
|
|
% * null
|
|
% Bind to a _null_ stream. Reading from such a stream
|
|
% returns end-of-file, writing produces no output
|
|
% * pipe(-Stream)
|
|
% Attach input and/or output to a Prolog stream.
|
|
%
|
|
% * cwd(+Directory)
|
|
% Run the new process in Directory. Directory can be a
|
|
% compound specification, which is converted using
|
|
% absolute_file_name/3.
|
|
% * env(+List)
|
|
% Specify the environment for the new process. List is
|
|
% a list of Name=Value terms. Note that the current
|
|
% implementation does not pass any environment variables.
|
|
% If unspecified, the environment is inherited from the
|
|
% Prolog process.
|
|
% * process(-PID)
|
|
% Unify PID with the process id of the created process.
|
|
% * detached(+Bool)
|
|
% In Unix: If =true=, detach the process from the terminal
|
|
% Currently mapped to setsid();
|
|
% In Windows: If =true=, detach the process from the current
|
|
% job via the CREATE_BREAKAWAY_FROM_JOB flag. In Vista and beyond,
|
|
% processes launched from the shell directly have the 'compatibility
|
|
% assistant' attached to them automatically unless they have a UAC
|
|
% manifest embedded in them. This means that you will get a
|
|
% permission denied error if you try and assign the newly-created
|
|
% PID to a job you create yourself.
|
|
% * window(+Bool)
|
|
% If =true=, create a window for the process (Windows only)
|
|
%
|
|
% If the user specifies the process(-PID) option, he *must* call
|
|
% process_wait/2 to reclaim the process. Without this option, the
|
|
% system will wait for completion of the process after the last
|
|
% pipe stream is closed.
|
|
%
|
|
% If the process is not waited for, it must succeed with status 0.
|
|
% If not, an process_error is raised.
|
|
%
|
|
% *|Windows notes|*
|
|
%
|
|
% On Windows this call is an interface to the CreateProcess() API.
|
|
% The commandline consists of the basename of Exe and the
|
|
% arguments formed from Args. Arguments are separated by a single
|
|
% space. If all characters satisfy iswalnum() it is unquoted. If
|
|
% the argument contains a double-quote it is quoted using single
|
|
% quotes. If both single and double quotes appear a domain_error
|
|
% is raised, otherwise double-quote are used.
|
|
%
|
|
% The CreateProcess() API has many options. Currently only the
|
|
% =CREATE_NO_WINDOW= options is supported through the
|
|
% window(+Bool) option. If omitted, the default is to use this
|
|
% option if the application has no console. Future versions are
|
|
% likely to support more window specific options and replace
|
|
% win_exec/2.
|
|
%
|
|
% *Examples*
|
|
%
|
|
% First, a very simple example that behaves the same as
|
|
% =|shell('ls -l')|=, except for error handling:
|
|
%
|
|
% ==
|
|
% ?- process_create(path(ls), ['-l'], []).
|
|
% ==
|
|
%
|
|
% @tbd The detach options is a no-op.
|
|
% @error process_error(Exe, Status) where Status is one of
|
|
% exit(Code) or killed(Signal). Raised if the process
|
|
% does not exit with status 0.
|
|
|
|
process_create(Exe, Args, Options) :-
|
|
exe_options(ExeOptions),
|
|
absolute_file_name(Exe, PlProg, ExeOptions),
|
|
must_be(list, Args),
|
|
maplist(map_arg, Args, Av),
|
|
prolog_to_os_filename(PlProg, Prog),
|
|
Term =.. [Prog|Av],
|
|
expand_cwd_option(Options, Options1),
|
|
process_create(Term, Options1).
|
|
|
|
exe_options(Options) :-
|
|
current_prolog_flag(windows, true), !,
|
|
Options = [ extensions(['',exe,com]), access(read) ].
|
|
exe_options(Options) :-
|
|
Options = [ access(execute) ].
|
|
|
|
expand_cwd_option(Options0, Options) :-
|
|
select_option(cwd(Spec), Options0, Options1), !,
|
|
( compound(Spec)
|
|
-> absolute_file_name(Spec, PlDir, [file_type(directory), access(read)]),
|
|
prolog_to_os_filename(PlDir, Dir),
|
|
Options = [cwd(Dir)|Options1]
|
|
; exists_directory(Spec)
|
|
-> Options = Options0
|
|
; existence_error(directory, Spec)
|
|
).
|
|
expand_cwd_option(Options, Options).
|
|
|
|
|
|
%% map_arg(+ArgIn, -Arg) is det.
|
|
%
|
|
% Map an individual argument. Primitives are either file(Spec) or
|
|
% an atomic value (atom, string, number). If ArgIn is a non-empty
|
|
% list, all elements are converted and the results are
|
|
% concatenated.
|
|
|
|
map_arg([], []) :- !.
|
|
map_arg(List, Arg) :-
|
|
is_list(List), !,
|
|
maplist(map_arg_prim, List, Prims),
|
|
atomic_list_concat(Prims, Arg).
|
|
map_arg(Prim, Arg) :-
|
|
map_arg_prim(Prim, Arg).
|
|
|
|
map_arg_prim(file(Spec), File) :- !,
|
|
( compound(Spec)
|
|
-> absolute_file_name(Spec, PlFile)
|
|
; PlFile = Spec
|
|
),
|
|
prolog_to_os_filename(PlFile, File).
|
|
map_arg_prim(Arg, Arg).
|
|
|
|
|
|
%% process_id(-PID) is det.
|
|
%
|
|
% True if PID is the process id of the running Prolog process.
|
|
%
|
|
% @deprecated Use current_prolog_flag(pid, PID)
|
|
|
|
process_id(PID) :-
|
|
current_prolog_flag(pid, PID).
|
|
|
|
%% process_id(+Process, -PID) is det.
|
|
%
|
|
% PID is the process id of Process. Given that they are united in
|
|
% SWI-Prolog, this is a simple unify.
|
|
|
|
process_id(PID, PID).
|
|
|
|
%% is_process(+PID) is semidet.
|
|
%
|
|
% True if PID might be a process. Succeeds for any positive
|
|
% integer.
|
|
|
|
is_process(PID) :-
|
|
integer(PID),
|
|
PID > 0.
|
|
|
|
%% process_release(+PID)
|
|
%
|
|
% Release process handle. In this implementation this is the same
|
|
% as process_wait(PID, _).
|
|
|
|
process_release(PID) :-
|
|
process_wait(PID, _).
|
|
|
|
%% process_wait(+PID, -Status) is det.
|
|
%% process_wait(+PID, -Status, +Options) is det.
|
|
%
|
|
% True if PID completed with Status. This call normally blocks
|
|
% until the process is finished. Options:
|
|
%
|
|
% * timeout(+Timeout)
|
|
% Default: =infinite=. If this option is a number, the
|
|
% waits for a maximum of Timeout seconds and unifies Status
|
|
% with =timeout= if the process does not terminate within
|
|
% Timeout. In this case PID is _not_ invalidated. On Unix
|
|
% systems only timeout 0 and =infinite= are supported. A
|
|
% 0-value can be used to poll the status of the process.
|
|
%
|
|
% * release(+Bool)
|
|
% Do/do not release the process. We do not support this flag
|
|
% and a domain_error is raised if release(false) is provided.
|
|
|
|
process_wait(PID, Status) :-
|
|
process_wait(PID, Status, []).
|
|
|
|
%% process_kill(+PID) is det.
|
|
%% process_kill(+PID, +Signal) is det.
|
|
%
|
|
% Send signal to process PID. Default is =term=. Signal is an
|
|
% integer, Unix signal name (e.g. =SIGSTOP=) or the more Prolog
|
|
% friendly variation one gets after removing =SIG= and downcase
|
|
% the result: =stop=. On Windows systems, Signal is ignored and
|
|
% the process is terminated using the TerminateProcess() API. On
|
|
% Windows systems PID must be obtained from process_create/3,
|
|
% while any PID is allowed on Unix systems.
|
|
%
|
|
% @compat SICStus does not accept the prolog friendly version. We
|
|
% choose to do so for compatibility with on_signal/3.
|
|
|
|
process_kill(PID) :-
|
|
process_kill(PID, term).
|
|
|
|
|
|
/*******************************
|
|
* MESSAGES *
|
|
*******************************/
|
|
|
|
:- multifile
|
|
prolog:error_message/3.
|
|
|
|
prolog:error_message(process_error(File, exit(Status))) -->
|
|
[ 'Process "~w": exit status: ~w'-[File, Status] ].
|
|
prolog:error_message(process_error(File, killed(Signal))) -->
|
|
[ 'Process "~w": killed by signal ~w'-[File, Signal] ].
|