Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113626
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/exile/process/exec.ex b/lib/exile/process/exec.ex
index d5f0ceb..a844d22 100644
--- a/lib/exile/process/exec.ex
+++ b/lib/exile/process/exec.ex
@@ -1,221 +1,216 @@
defmodule Exile.Process.Exec do
@moduledoc false
alias Exile.Process.Nif
alias Exile.Process.Pipe
+ alias Exile.Process.State
@type args :: %{
cmd_with_args: [String.t()],
cd: String.t(),
env: [{String.t(), String.t()}]
}
- @spec start(args, boolean()) :: %{
+ @spec start(args, State.stderr_mode()) :: %{
port: port,
stdin: non_neg_integer(),
stdout: non_neg_integer(),
stderr: non_neg_integer()
}
- def start(
- %{
- cmd_with_args: cmd_with_args,
- cd: cd,
- env: env
- },
- stderr
- ) do
+ def start(args, stderr) do
+ %{cmd_with_args: cmd_with_args, cd: cd, env: env} = args
socket_path = socket_path()
{:ok, sock} = :socket.open(:local, :stream, :default)
try do
:ok = socket_bind(sock, socket_path)
:ok = :socket.listen(sock)
spawner_cmdline_args = [socket_path, to_string(stderr) | cmd_with_args]
port_opts =
[:nouse_stdio, :exit_status, :binary, args: spawner_cmdline_args] ++
prune_nils(env: env, cd: cd)
port = Port.open({:spawn_executable, spawner_path()}, port_opts)
{:os_pid, os_pid} = Port.info(port, :os_pid)
Exile.Watcher.watch(self(), os_pid, socket_path)
{stdin_fd, stdout_fd, stderr_fd} = receive_fds(sock, stderr)
%{port: port, stdin: stdin_fd, stdout: stdout_fd, stderr: stderr_fd}
after
:socket.close(sock)
File.rm!(socket_path)
end
end
@spec normalize_exec_args(nonempty_list(), keyword()) ::
{:ok,
%{
cmd_with_args: nonempty_list(),
cd: charlist,
env: env,
stderr: :console | :disable | :consume
}}
| {:error, String.t()}
def normalize_exec_args(cmd_with_args, opts) do
with {:ok, cmd} <- normalize_cmd(cmd_with_args),
{:ok, args} <- normalize_cmd_args(cmd_with_args),
:ok <- validate_opts_fields(opts),
{:ok, cd} <- normalize_cd(opts[:cd]),
{:ok, stderr} <- normalize_stderr(opts[:stderr]),
{:ok, env} <- normalize_env(opts[:env]) do
{:ok, %{cmd_with_args: [cmd | args], cd: cd, env: env, stderr: stderr}}
end
end
@spec spawner_path :: String.t()
defp spawner_path do
:filename.join(:code.priv_dir(:exile), "spawner")
end
@socket_timeout 2000
- @spec receive_fds(:socket.socket(), boolean) :: {Pipe.fd(), Pipe.fd(), Pipe.fd()}
- defp receive_fds(lsock, stderr) do
+ @spec receive_fds(:socket.socket(), State.stderr_mode()) :: {Pipe.fd(), Pipe.fd(), Pipe.fd()}
+ defp receive_fds(lsock, stderr_mode) do
{:ok, sock} = :socket.accept(lsock, @socket_timeout)
try do
{:ok, msg} = :socket.recvmsg(sock, @socket_timeout)
%{ctrl: [%{data: data, level: :socket, type: :rights}]} = msg
<<stdin_fd::native-32, stdout_fd::native-32, stderr_fd::native-32, _::binary>> = data
# FDs are managed by the NIF resource life-cycle
{:ok, stdout} = Nif.nif_create_fd(stdout_fd)
{:ok, stdin} = Nif.nif_create_fd(stdin_fd)
{:ok, stderr} =
if stderr == :consume do
Nif.nif_create_fd(stderr_fd)
else
{:ok, nil}
end
{stdin, stdout, stderr}
after
:socket.close(sock)
end
end
# skip type warning till we change min OTP version to 24.
@dialyzer {:nowarn_function, socket_bind: 2}
defp socket_bind(sock, path) do
case :socket.bind(sock, %{family: :local, path: path}) do
:ok -> :ok
# for compatibility with OTP version < 24
{:ok, _} -> :ok
other -> other
end
end
@spec socket_path() :: String.t()
defp socket_path do
str = :crypto.strong_rand_bytes(16) |> Base.url_encode64() |> binary_part(0, 16)
path = Path.join(System.tmp_dir!(), str)
_ = :file.delete(path)
path
end
@spec prune_nils(keyword()) :: keyword()
defp prune_nils(kv) do
Enum.reject(kv, fn {_, v} -> is_nil(v) end)
end
@spec normalize_cmd(nonempty_list()) :: {:ok, nonempty_list()} | {:error, binary()}
defp normalize_cmd(arg) do
case arg do
[cmd | _] when is_binary(cmd) ->
path = System.find_executable(cmd)
if path do
{:ok, to_charlist(path)}
else
{:error, "command not found: #{inspect(cmd)}"}
end
_ ->
{:error, "`cmd_with_args` must be a list of strings, Please check the documentation"}
end
end
defp normalize_cmd_args([_ | args]) do
if Enum.all?(args, &is_binary/1) do
{:ok, Enum.map(args, &to_charlist/1)}
else
{:error, "command arguments must be list of strings. #{inspect(args)}"}
end
end
@spec normalize_cd(binary) :: {:ok, charlist()} | {:error, String.t()}
defp normalize_cd(cd) do
case cd do
nil ->
{:ok, ''}
cd when is_binary(cd) ->
if File.exists?(cd) && File.dir?(cd) do
{:ok, to_charlist(cd)}
else
{:error, "`:cd` must be valid directory path"}
end
_ ->
{:error, "`:cd` must be a binary string"}
end
end
@type env :: list({String.t(), String.t()})
@spec normalize_env(env) :: {:ok, env} | {:error, String.t()}
defp normalize_env(env) do
case env do
nil ->
{:ok, []}
env when is_list(env) or is_map(env) ->
env =
Enum.map(env, fn {key, value} ->
{to_charlist(key), to_charlist(value)}
end)
{:ok, env}
_ ->
{:error, "`:env` must be a map or list of `{string, string}`"}
end
end
@spec normalize_stderr(stderr :: :console | :disable | :consume | nil) ::
{:ok, :console | :disable | :consume} | {:error, String.t()}
defp normalize_stderr(stderr) do
case stderr do
nil ->
{:ok, :console}
stderr when stderr in [:console, :disable, :consume] ->
{:ok, stderr}
_ ->
{:error, ":stderr must be an atom and one of :console, :disable, :consume"}
end
end
@spec validate_opts_fields(keyword) :: :ok | {:error, String.t()}
defp validate_opts_fields(opts) do
{_, additional_opts} = Keyword.split(opts, [:cd, :env, :stderr])
if Enum.empty?(additional_opts) do
:ok
else
{:error, "invalid opts: #{inspect(additional_opts)}"}
end
end
end
diff --git a/lib/exile/process/state.ex b/lib/exile/process/state.ex
index 678c07f..60f637f 100644
--- a/lib/exile/process/state.ex
+++ b/lib/exile/process/state.ex
@@ -1,99 +1,101 @@
defmodule Exile.Process.State do
@moduledoc false
alias Exile.Process.Exec
alias Exile.Process.Operations
alias Exile.Process.Pipe
alias __MODULE__
@type read_mode :: :stdout | :stderr | :stdout_or_stderr
+ @type stderr_mode :: :console | :disable | :consume
+
@type pipes :: %{
stdin: Pipe.t(),
stdout: Pipe.t(),
stderr: Pipe.t()
}
@type status ::
:init
| :running
| {:exit, non_neg_integer()}
| {:exit, {:error, error :: term}}
@type t :: %__MODULE__{
args: Exec.args(),
owner: pid,
port: port(),
pipes: pipes,
status: status,
- stderr: :console | :disable | :consume,
+ stderr: stderr_mode,
operations: Operations.t(),
exit_ref: reference(),
monitor_ref: reference()
}
defstruct [
:args,
:owner,
:port,
:pipes,
:status,
:stderr,
:operations,
:exit_ref,
:monitor_ref
]
alias __MODULE__
@spec pipe(State.t(), name :: Pipe.name()) :: {:ok, Pipe.t()} | {:error, :invalid_name}
def pipe(%State{} = state, name) do
if name in [:stdin, :stdout, :stderr] do
{:ok, Map.fetch!(state.pipes, name)}
else
{:error, :invalid_name}
end
end
@spec put_pipe(State.t(), name :: Pipe.name(), Pipe.t()) :: {:ok, t} | {:error, :invalid_name}
def put_pipe(%State{} = state, name, pipe) do
if name in [:stdin, :stdout, :stderr] do
pipes = Map.put(state.pipes, name, pipe)
state = %State{state | pipes: pipes}
{:ok, state}
else
{:error, :invalid_name}
end
end
@spec pipe_name_for_fd(State.t(), fd :: Pipe.fd()) :: Pipe.name()
def pipe_name_for_fd(state, fd) do
pipe =
state.pipes
|> Map.values()
|> Enum.find(&(&1.fd == fd))
pipe.name
end
@spec put_operation(State.t(), Operations.operation()) :: {:ok, t} | {:error, term}
def put_operation(%State{operations: ops} = state, operation) do
with {:ok, ops} <- Operations.put(ops, operation) do
{:ok, %State{state | operations: ops}}
end
end
@spec pop_operation(State.t(), Operations.name()) ::
{:ok, Operations.operation(), t} | {:error, term}
def pop_operation(%State{operations: ops} = state, name) do
with {:ok, operation, ops} <- Operations.pop(ops, name) do
{:ok, operation, %State{state | operations: ops}}
end
end
@spec set_status(State.t(), status) :: State.t()
def set_status(state, status) do
%State{state | status: status}
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 1:45 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39809
Default Alt Text
(9 KB)
Attached To
Mode
R14 exile
Attached
Detach File
Event Timeline
Log In to Comment