%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(net_adm).
-export([host_file/0,
localhost/0,
names/0, names/1,
ping_list/1,
world/0,world/1,
world_list/1, world_list/2,
dns_hostname/1,
ping/1]).
%%------------------------------------------------------------------------
-type verbosity() :: 'silent' | 'verbose'.
%%------------------------------------------------------------------------
%% Try to read .hosts.erlang file in
%% 1. cwd , 2. $HOME 3. init:root_dir()
-spec host_file() -> [atom()] | {'error',atom() | {integer(),atom(),_}}.
host_file() ->
Home = case init:get_argument(home) of
{ok, [[H]]} -> [H];
_ -> []
end,
case file:path_consult(["."] ++ Home ++ [code:root_dir()], ".hosts.erlang") of
{ok, Hosts, _} -> Hosts;
Error -> Error
end.
%% Check whether a node is up or down
%% side effect: set up a connection to Node if there not yet is one.
-spec ping(atom()) -> 'pang' | 'pong'.
ping(Node) when is_atom(Node) ->
case catch gen:call({net_kernel, Node},
'$gen_call',
{is_auth, node()},
infinity) of
{ok, yes} -> pong;
_ ->
erlang:disconnect_node(Node),
pang
end.
-spec localhost() -> string().
localhost() ->
{ok, Host} = inet:gethostname(),
case inet_db:res_option(domain) of
"" -> Host;
Domain -> Host ++ "." ++ Domain
end.
-spec names() -> {'ok', [{string(), integer()}]} | {'error', _}.
names() ->
names(localhost()).
-spec names(atom() | string()) -> {'ok', [{string(), integer()}]} | {'error', _}.
names(Hostname) ->
case inet:gethostbyname(Hostname) of
{ok, {hostent, _Name, _ , _Af, _Size, [Addr | _]}} ->
erl_epmd:names(Addr);
Else ->
Else
end.
-spec dns_hostname(atom() | string()) ->
{'ok', string()} | {'error', atom() | string()}.
dns_hostname(Hostname) ->
case inet:gethostbyname(Hostname) of
{ok,{hostent, Name, _ , _Af, _Size, _Addr}} ->
{ok, Name};
_ ->
{error, Hostname}
end.
%% A common situation in "life" is to have a configuration file with a list
%% of nodes, and then at startup, all nodes in the list are ping'ed
%% this can lead to no end of troubles if two disconnected nodes
%% simultaneously ping each other.
%% Use this function in order to do it safely.
%% It assumes a working global.erl which ensures a fully
%% connected network.
%% Had the erlang runtime system been able to fully cope with
%% the possibility of two simultaneous (unix) connects, this function would
%% merley be lists:map({net_adm, ping}, [], Nodelist).
%% It is also assumed, that the same (identical) Nodelist is given to all
%% nodes which are to perform this call (possibly simultaneously).
%% Even this code has a flaw, and that is the case where two
%% nodes simultaneously and without *any* other already
%% running nodes execute this code. :-(
-spec ping_list([atom()]) -> [atom()].
ping_list(Nodelist) ->
net_kernel:monitor_nodes(true),
Sofar = ping_first(Nodelist, nodes()),
collect_new(Sofar, Nodelist).
ping_first([], _S) ->
[];
ping_first([Node|Nodes], S) ->
case lists:member(Node, S) of
true -> [Node | ping_first(Nodes, S)];
false ->
case ping(Node) of
pong -> [Node];
pang -> ping_first(Nodes, S)
end
end.
collect_new(Sofar, Nodelist) ->
receive
{nodeup, Node} ->
case lists:member(Node, Nodelist) of
true ->
collect_new(Sofar, Nodelist);
false ->
collect_new([Node | Sofar], Nodelist)
end
after 3000 ->
net_kernel:monitor_nodes(false),
Sofar
end.
%% This function polls a set of hosts according to a file called
%% .hosts.erlang that need to reside either in the current directory
%% or in your home directory. (The current directory is tried first.)
%% world() returns a list of all nodes on the network that can be
%% found (including ourselves). Note: the $HOME variable is inspected.
%%
%% Added possibility to supply a list of hosts instead of reading
%% the .hosts.erlang file. 971016 [email protected]
%% e.g.
%% net_adm:world_list(['elrond.du.etx.ericsson.se', 'thorin.du.etx.ericsson.se']).
-spec world() -> [node()].
world() ->
world(silent).
-spec world(verbosity()) -> [node()].
world(Verbose) ->
case net_adm:host_file() of
{error,R} -> exit({error, R});
Hosts -> expand_hosts(Hosts, Verbose)
end.
-spec world_list([atom()]) -> [node()].
world_list(Hosts) when is_list(Hosts) ->
expand_hosts(Hosts, silent).
-spec world_list([atom()], verbosity()) -> [node()].
world_list(Hosts, Verbose) when is_list(Hosts) ->
expand_hosts(Hosts, Verbose).
expand_hosts(Hosts, Verbose) ->
lists:flatten(collect_nodes(Hosts, Verbose)).
collect_nodes([], _) -> [];
collect_nodes([Host|Tail], Verbose) ->
case collect_host_nodes(Host, Verbose) of
nil ->
collect_nodes(Tail, Verbose);
L ->
[L|collect_nodes(Tail, Verbose)]
end.
collect_host_nodes(Host, Verbose) ->
case names(Host) of
{ok, Namelist} ->
do_ping(Namelist, atom_to_list(Host), Verbose);
_ ->
nil
end.
do_ping(Names, Host0, Verbose) ->
case longshort(Host0) of
ignored -> [];
Host -> do_ping_1(Names, Host, Verbose)
end.
do_ping_1([], _Host, _Verbose) ->
[];
do_ping_1([{Name, _} | Rest], Host, Verbose) ->
Node = list_to_atom(Name ++ "@" ++ longshort(Host)),
verbose(Verbose, "Pinging ~w -> ", [Node]),
Result = ping(Node),
verbose(Verbose, "~p\n", [Result]),
case Result of
pong ->
[Node | do_ping_1(Rest, Host, Verbose)];
pang ->
do_ping_1(Rest, Host, Verbose)
end.
verbose(verbose, Format, Args) ->
io:format(Format, Args);
verbose(_, _, _) ->
ok.
longshort(Host) ->
case net_kernel:longnames() of
false -> uptodot(Host);
true -> Host;
ignored -> ignored
end.
uptodot([$.|_]) -> [];
uptodot([])-> [];
uptodot([H|T]) -> [H|uptodot(T)].