Current section
Files
Jump to
Current section
Files
lib/exexec.ex
defmodule Exexec do
@moduledoc """
Execute and control OS processes from Elixir.
An idiomatic Elixir wrapper for Serge Aleynikov's excellent
[erlexec](https://github.com/saleyn/erlexec), Exexec provides an Elixir
interface as well as some nice Elixir-y goodies on top.
"""
import Exexec.ToErl
@type command :: String.t() | [Path.t() | [String.t()]]
@type os_pid :: non_neg_integer
@type gid :: non_neg_integer
@type output_file_option ::
{:append, boolean}
| {:mode, non_neg_integer}
@type output_device :: :stdout | :stderr
@type output_file_options :: [output_file_option]
@type output_device_option ::
boolean
| :null
| :close
| :print
| Path.t()
| {Path.t(), output_file_options}
| pid
| (output_device, os_pid, binary -> any)
@type command_option ::
{:monitor, boolean}
| {:sync, boolean}
| {:executable, Path.t()}
| {:cd, Path.t()}
| {:env, %{String.t() => String.t()}}
| {:kill_command, String.t()}
| {:kill_timeout, non_neg_integer}
| {:kill_group, boolean}
| {:group, String.t()}
| {:user, String.t()}
| {:success_exit_code, exit_code}
| {:nice, -20..20}
| {:stdin, boolean | :null | :close | Path.t()}
| {:stdout, :stderr | output_device_option}
| {:stderr, :stdout | output_device_option}
| {:pty, boolean}
@type command_options :: [command_option]
@type exec_option ::
{:debug, boolean | non_neg_integer}
| {:root, boolean}
| {:verbose, boolean}
| {:args, [String.t()]}
| {:alarm, non_neg_integer}
| {:user, String.t()}
| {:limit_users, [String.t()]}
| {:port_path, Path.t()}
| {:env, %{String.t() => String.t()}}
@type exec_options :: [exec_option]
@type signal :: pos_integer
@type on_run ::
{:ok, pid, os_pid}
| {:ok, [{output_device, [binary]}]}
| {:error, any}
@type exit_code :: non_neg_integer
@doc """
Send `signal` to `pid`.
`pid` can be an `Exexec` pid, OS pid, or port.
"""
@spec kill(pid | os_pid | port, signal) :: :ok | {:error, any}
defdelegate kill(pid, signal), to: :exec
@doc """
Start an `Exexec` process to manage existing `os_pid` with options `options`.
`os_pid` can also be a port.
"""
@spec manage(os_pid | port) :: {:ok, pid, os_pid} | {:error, any}
@spec manage(os_pid | port, command_options) :: {:ok, pid, os_pid} | {:error, any}
def manage(os_pid, options \\ []),
do: :exec.manage(os_pid, command_options_to_erl(options))
@doc """
Returns the OS pid for `Exexec` process `pid`.
"""
@spec os_pid(pid) :: {:ok, os_pid} | {:error, any}
def os_pid(pid) do
case :exec.ospid(pid) do
{:error, reason} -> {:error, reason}
os_pid -> {:ok, os_pid}
end
end
@doc """
Returns the `Exexec` pid for `os_pid`.
"""
@spec pid(os_pid) :: {:ok, pid} | {:error, any}
def pid(os_pid) do
case :exec.pid(os_pid) do
{:error, reason} -> {:error, reason}
:undefined -> {:error, :undefined}
pid -> {:ok, pid}
end
end
@doc """
Run an external `command` with `options`.
"""
@spec run(command) :: on_run
@spec run(command, command_options) :: on_run
def run(command, options \\ []) do
command = command_to_erl(command)
options = command_options_to_erl(options)
:exec.run(command, options)
end
@doc """
Run an external `command` with `options`, linking to the current process.
If the external process exits with code 0, the linked process will not exit.
"""
@spec run_link(command) :: on_run
@spec run_link(command, command_options) :: on_run
def run_link(command, options \\ []) do
command = command_to_erl(command)
options = command_options_to_erl(options)
:exec.run_link(command, options)
end
@doc """
Send `data` to the stdin of `pid`.
`pid` can be an `Exexec` pid or an OS pid.
"""
@spec send(pid | os_pid, binary | :eof) :: :ok
defdelegate send(pid, data), to: :exec
@doc """
Change group ID of `os_pid` to `gid`.
"""
@spec set_gid(os_pid, gid) :: :ok | {:error, any}
defdelegate set_gid(os_pid, gid), to: :exec, as: :setpgid
@doc """
Convert integer `signal` to atom, or return `signal`.
"""
@spec signal(signal) :: atom | integer
defdelegate signal(signal), to: :exec
@doc """
Start `Exexec`.
"""
@spec start() :: {:ok, pid} | {:error, any}
defdelegate start(), to: :exec
@doc """
Start `Exexec` with `options`.
"""
@spec start(exec_options) :: {:ok, pid} | {:error, any}
def start(options) do
:exec.start(exec_options_to_erl(options))
end
@doc """
Start `Exexec` and link to calling process.
"""
@spec start_link :: {:ok, pid} | {:error, any}
def start_link(), do: start_link([])
@doc """
Start `Exexec` with `options` and link to calling process.
"""
@spec start_link(exec_options) :: {:ok, pid} | {:error, any}
def start_link(options) do
:exec.start_link(exec_options_to_erl(options))
end
@doc """
Interpret `exit_code`.
If the program exited by signal, returns `{:signal, signal, core}` where `signal`
is the atom or integer signal and `core` is whether a core file was generated.
"""
@spec status(exit_code) :: {:status, signal} | {:signal, signal | :atom, boolean}
defdelegate status(exit_code), to: :exec
@doc """
Stop `pid`.
`pid` can be an `Exexec` pid, OS pid, or port.
The OS process is terminated gracefully. If `:kill_command` was specified,
that command is executed and a timer is started. If the process doesn't exit
immediately, then by default after 5 seconds SIGKILL will be sent to the process.
"""
@spec stop(pid | os_pid | port) :: :ok | {:error, any}
defdelegate stop(pid), to: :exec
@doc """
Stop `pid` and wait for it to exit for `timeout` milliseconds.
See `Exexec.stop/1`.
"""
@spec stop_and_wait(pid | os_pid | port) :: :ok | {:error, any}
@spec stop_and_wait(pid | os_pid | port, integer) :: :ok | {:error, any}
def stop_and_wait(pid, timeout \\ 5_000), do: :exec.stop_and_wait(pid, timeout)
@doc """
Return a list of OS pids managed by `Exexec`.
"""
@spec which_children() :: [os_pid]
defdelegate which_children(), to: :exec
end