Current section
Files
Jump to
Current section
Files
lib/epmd_up.ex
defmodule EpmdUp do
@moduledoc """
A simple Elixir module to check and manage the Erlang Port Mapper Daemon (`epmd`).
"""
require Logger
@epmd_port 4369
@kill_req <<0, 1, 107>>
@doc """
Starts the Erlang Port Mapper Daemon (`epmd`) if it's not already running.
"""
@spec activate() :: :ok | {:error, term()}
def activate() do
case active?() do
true -> :ok
_ -> start_epmd()
end
end
@doc """
Checks if the Erlang Port Mapper Daemon (`epmd`) is active and accepting connections.
This function attempts to establish a TCP connection to the `epmd` port (4369) on localhost.
If the connection is successful, it means `epmd` is running and accepting connections.
## Returns
* `true` - if `epmd` is active and accepting connections
* `false` - if `epmd` is not running or not accepting connections
"""
@spec active?() :: boolean()
def active?() do
case :net_adm.names() do
{:ok, _} -> true
{:error, _} -> false
end
end
@doc """
Stops the Erlang Port Mapper Daemon (`epmd`)
using [Kill EPMD](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#kill-epmd)
if it's running
"""
@spec deactivate() :: :ok | {:error, term()}
def deactivate() do
case active?() do
false -> :ok
_ -> stop_epmd()
end
end
@doc """
Finds the full path of the Erlang Port Mapper Daemon (`epmd`) executable in the system.
Returns `nil` if the executable cannot be found in the system's PATH.
"""
@spec find_epmd_executable() :: binary() | nil
def find_epmd_executable() do
System.find_executable("epmd")
end
defp start_epmd() do
case find_epmd_executable() do
nil ->
{:error, "Not found epmd"}
epmd_cmd ->
spawn(fn -> launch_epmd(epmd_cmd) end)
Logger.info("waiting launching epmd...")
wait_launching_epmd(5)
end
end
defp stop_epmd() do
case :gen_tcp.connect(~c'localhost', @epmd_port, [:binary, active: false], 1000) do
{:ok, socket} ->
:gen_tcp.send(socket, @kill_req)
Logger.info("waiting terminating epmd...")
wait_terminating_epmd(socket, 5)
{:error, reason} ->
Logger.warning("Fail to connect due to #{inspect(reason)}.")
{:error, reason}
end
end
defp launch_epmd(epmd_cmd) do
case System.cmd(epmd_cmd, [], parallelism: true) do
{result, 0} ->
Logger.info("epmd: #{result}")
:ok
{_, exit_code} ->
Logger.info("epmd: error_code: #{exit_code}")
{:error, exit_code}
end
end
defp wait_launching_epmd(0), do: {:error, "Fail to launch epmd."}
defp wait_launching_epmd(count) do
if active?() do
:ok
else
Process.sleep(1000)
wait_launching_epmd(count - 1)
end
end
defp wait_terminating_epmd(socket, count) do
case :gen_tcp.recv(socket, 2, 5000) do
{:ok, "OK"} -> :ok
{:ok, v} -> {:error, "Unknown ack #{inspect(v)}"}
{:error, reason} -> {:error, reason}
end
wait_terminating_epmd_sub(count)
end
defp wait_terminating_epmd_sub(0), do: {:error, "Fail to terminating epmd."}
defp wait_terminating_epmd_sub(count) do
if active?() do
Process.sleep(1000)
wait_terminating_epmd_sub(count - 1)
else
:ok
end
end
end