defmodule Systemd.Manager do
@moduledoc """
Client for `org.freedesktop.systemd1.Manager`.
"""
alias Systemd.{DBus, Error, Job, JobStatus, Unit, UnitFileOperation, UnitFileStatus, UnitObject}
alias Systemd.Manager.Options
alias Systemd.TransientUnit.{AuxUnit, Property}
@destination "org.freedesktop.systemd1"
@path "/org/freedesktop/systemd1"
@interface "org.freedesktop.systemd1.Manager"
@doc """
Connects to the system bus.
"""
@spec connect(keyword()) :: {:ok, pid()} | {:error, Error.t()}
def connect(opts \\ []) do
opts = Options.new(opts)
DBus.connect(opts.bus, [])
end
@doc """
Lists currently loaded systemd units.
Accepts either an existing D-Bus connection PID or connection options. Passing
options opens a short-lived connection.
"""
@spec list_units(pid() | keyword()) :: {:ok, [Unit.t()]} | {:error, Error.t()}
def list_units(conn_or_opts \\ [])
def list_units(conn) when is_pid(conn) do
with {:ok, [units]} <- call(conn, "ListUnits") do
{:ok, Enum.map(units, &Unit.from_list_units_row/1)}
end
end
def list_units(opts) when is_list(opts) do
with {:ok, conn} <- connect(opts) do
list_units(conn)
end
end
@doc """
Lists currently queued jobs.
"""
@spec list_jobs(pid()) :: {:ok, [JobStatus.t()]} | {:error, Error.t()}
def list_jobs(conn) when is_pid(conn) do
with {:ok, [jobs]} <- call(conn, "ListJobs") do
{:ok, Enum.map(jobs, &JobStatus.from_dbus/1)}
end
end
@doc """
Gets the D-Bus object path for a loaded unit.
"""
@spec get_unit(pid(), String.t()) :: {:ok, UnitObject.t()} | {:error, Error.t()}
def get_unit(conn, unit_name) when is_pid(conn) and is_binary(unit_name) do
with {:ok, [object_path]} <- call(conn, "GetUnit", [unit_name], "s") do
{:ok, UnitObject.new(object_path, unit_name)}
end
end
@doc """
Gets the D-Bus object path for a unit by main process ID.
"""
@spec get_unit_by_pid(pid(), non_neg_integer()) :: {:ok, UnitObject.t()} | {:error, Error.t()}
def get_unit_by_pid(conn, pid) when is_pid(conn) and is_integer(pid) and pid >= 0 do
with {:ok, [object_path]} <- call(conn, "GetUnitByPID", [pid], "u") do
{:ok, UnitObject.new(object_path)}
end
end
@doc """
Returns unit files known to systemd and their enablement state.
"""
@spec list_unit_files(pid()) :: {:ok, [UnitFileStatus.t()]} | {:error, Error.t()}
def list_unit_files(conn) when is_pid(conn) do
with {:ok, [unit_files]} <- call(conn, "ListUnitFiles") do
{:ok, Enum.map(unit_files, &UnitFileStatus.from_dbus/1)}
end
end
@doc """
Returns the enablement state of a unit file.
"""
@spec unit_file_state(pid(), String.t()) :: {:ok, String.t()} | {:error, Error.t()}
def unit_file_state(conn, unit_name) when is_pid(conn) and is_binary(unit_name) do
with {:ok, [state]} <- call(conn, "GetUnitFileState", [unit_name], "s") do
{:ok, state}
end
end
@doc """
Starts a unit and returns the queued systemd job.
"""
@spec start_unit(pid(), String.t(), keyword()) :: {:ok, Job.t()} | {:error, Error.t()}
def start_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "StartUnit", unit_name, opts)
end
@doc """
Stops a unit and returns the queued systemd job.
"""
@spec stop_unit(pid(), String.t(), keyword()) :: {:ok, Job.t()} | {:error, Error.t()}
def stop_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "StopUnit", unit_name, opts)
end
@doc """
Restarts a unit and returns the queued systemd job.
"""
@spec restart_unit(pid(), String.t(), keyword()) :: {:ok, Job.t()} | {:error, Error.t()}
def restart_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "RestartUnit", unit_name, opts)
end
@doc """
Reloads a unit and returns the queued systemd job.
"""
@spec reload_unit(pid(), String.t(), keyword()) :: {:ok, Job.t()} | {:error, Error.t()}
def reload_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "ReloadUnit", unit_name, opts)
end
@doc """
Tries to restart a unit only if it is already active.
"""
@spec try_restart_unit(pid(), String.t(), keyword()) :: {:ok, Job.t()} | {:error, Error.t()}
def try_restart_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "TryRestartUnit", unit_name, opts)
end
@doc """
Reloads a unit if supported, otherwise restarts it.
"""
@spec reload_or_restart_unit(pid(), String.t(), keyword()) ::
{:ok, Job.t()} | {:error, Error.t()}
def reload_or_restart_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "ReloadOrRestartUnit", unit_name, opts)
end
@doc """
Reloads a unit if supported, otherwise tries to restart it only if active.
"""
@spec reload_or_try_restart_unit(pid(), String.t(), keyword()) ::
{:ok, Job.t()} | {:error, Error.t()}
def reload_or_try_restart_unit(conn, unit_name, opts \\ []) do
unit_operation(conn, "ReloadOrTryRestartUnit", unit_name, opts)
end
@doc """
Resets failed state for a unit.
"""
@spec reset_failed_unit(pid(), String.t()) :: :ok | {:error, Error.t()}
def reset_failed_unit(conn, unit_name) when is_pid(conn) and is_binary(unit_name) do
with {:ok, []} <- call(conn, "ResetFailedUnit", [unit_name], "s"), do: :ok
end
@doc """
Sends a Unix signal to processes belonging to a unit.
"""
@spec kill_unit(pid(), String.t(), String.t(), integer()) :: :ok | {:error, Error.t()}
def kill_unit(conn, unit_name, who \\ "all", signal \\ 15)
def kill_unit(conn, unit_name, who, signal)
when is_pid(conn) and is_binary(unit_name) and is_binary(who) and is_integer(signal) do
with {:ok, []} <- call(conn, "KillUnit", [unit_name, who, signal], "ssi"), do: :ok
end
@doc """
Starts a transient unit and returns the queued systemd job.
"""
@spec start_transient_unit(pid(), String.t(), [Property.t()], keyword()) ::
{:ok, Job.t()} | {:error, Error.t()}
def start_transient_unit(conn, unit_name, properties, opts \\ []) do
mode = Options.new(opts).mode
properties = Enum.map(properties, &Property.to_dbus/1)
aux_units = opts |> Keyword.get(:aux_units, []) |> Enum.map(&AuxUnit.to_dbus/1)
with {:ok, [object_path]} <-
call(
conn,
"StartTransientUnit",
[unit_name, mode, properties, aux_units],
"ssa(sv)a(sa(sv))"
) do
{:ok, %Job{object_path: object_path}}
end
end
@doc """
Enables unit files.
"""
@spec enable_unit_files(pid(), [String.t()], keyword()) ::
{:ok, UnitFileOperation.t()} | {:error, Error.t()}
def enable_unit_files(conn, files, opts \\ []) do
opts = Options.new(opts)
runtime? = opts.runtime
force? = opts.force
with {:ok, [carries_install_info?, changes]} <-
call(conn, "EnableUnitFiles", [files, runtime?, force?], "asbb") do
{:ok, UnitFileOperation.new(changes, carries_install_info: carries_install_info?)}
end
end
@doc """
Disables unit files.
"""
@spec disable_unit_files(pid(), [String.t()], keyword()) ::
{:ok, UnitFileOperation.t()} | {:error, Error.t()}
def disable_unit_files(conn, files, opts \\ []) do
runtime? = Options.new(opts).runtime
with {:ok, [changes]} <- call(conn, "DisableUnitFiles", [files, runtime?], "asb") do
{:ok, UnitFileOperation.new(changes)}
end
end
@doc """
Masks unit files.
"""
@spec mask_unit_files(pid(), [String.t()], keyword()) ::
{:ok, UnitFileOperation.t()} | {:error, Error.t()}
def mask_unit_files(conn, files, opts \\ []) do
opts = Options.new(opts)
runtime? = opts.runtime
force? = opts.force
with {:ok, [changes]} <- call(conn, "MaskUnitFiles", [files, runtime?, force?], "asbb") do
{:ok, UnitFileOperation.new(changes)}
end
end
@doc """
Unmasks unit files.
"""
@spec unmask_unit_files(pid(), [String.t()], keyword()) ::
{:ok, UnitFileOperation.t()} | {:error, Error.t()}
def unmask_unit_files(conn, files, opts \\ []) do
runtime? = Options.new(opts).runtime
with {:ok, [changes]} <- call(conn, "UnmaskUnitFiles", [files, runtime?], "asb") do
{:ok, UnitFileOperation.new(changes)}
end
end
@doc """
Links unit files into systemd's search path.
"""
@spec link_unit_files(pid(), [String.t()], keyword()) ::
{:ok, UnitFileOperation.t()} | {:error, Error.t()}
def link_unit_files(conn, files, opts \\ []) do
opts = Options.new(opts)
runtime? = opts.runtime
force? = opts.force
with {:ok, [changes]} <- call(conn, "LinkUnitFiles", [files, runtime?, force?], "asbb") do
{:ok, UnitFileOperation.new(changes)}
end
end
@doc """
Reloads systemd manager configuration (`daemon-reload`).
"""
@spec reload(pid()) :: :ok | {:error, Error.t()}
def reload(conn) when is_pid(conn) do
with {:ok, []} <- call(conn, "Reload"), do: :ok
end
defp unit_operation(conn, member, unit_name, opts) do
mode = Options.new(opts).mode
with {:ok, [object_path]} <- call(conn, member, [unit_name, mode], "ss") do
{:ok, %Job{object_path: object_path}}
end
end
defp call(conn, member, body \\ [], signature \\ "") do
DBus.call_body(conn,
destination: @destination,
path: @path,
interface: @interface,
member: member,
signature: signature,
body: body
)
end
end