1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(net_adm).
21-export([host_file/0,
22	 localhost/0,
23	 names/0, names/1,
24	 ping_list/1,
25	 world/0,world/1,
26	 world_list/1, world_list/2,
27	 dns_hostname/1,
28	 ping/1]).
29
30%%------------------------------------------------------------------------
31
32-type verbosity() :: 'silent' | 'verbose'.
33
34%%------------------------------------------------------------------------
35
36%% Try to read .hosts.erlang file in
37%% 1. cwd , 2. $HOME 3. init:root_dir()
38
39-spec host_file() -> Hosts | {error, Reason} when
40      Hosts :: [Host :: atom()],
41      %% Copied from file:path_consult/2:
42      Reason :: file:posix() | badarg | terminated | system_limit
43              | {Line :: integer(), Mod :: module(), Term :: term()}.
44
45host_file() ->
46    Home = case init:get_argument(home) of
47	       {ok, [[H]]} -> [H];
48	       _ -> []
49	   end,
50    case file:path_consult(["."] ++ Home ++ [code:root_dir()], ".hosts.erlang") of
51	{ok, Hosts, _} -> Hosts;
52	Error -> Error
53    end.
54
55%% Check whether a node is up or down
56%%  side effect: set up a connection to Node if there not yet is one.
57
58-spec ping(Node) -> pong | pang when
59      Node :: atom().
60
61ping(Node) when is_atom(Node) ->
62    case catch gen:call({net_kernel, Node},
63			'$gen_call',
64			{is_auth, node()},
65			infinity) of
66	{ok, yes} -> pong;
67	_ ->
68	    erlang:disconnect_node(Node),
69	    pang
70    end.
71
72-spec localhost() -> Name when
73      Name :: string().
74
75localhost() ->
76    {ok, Host} = inet:gethostname(),
77    case inet_db:res_option(domain) of
78	"" -> Host;
79	Domain -> Host ++ "." ++ Domain
80    end.
81
82
83-spec names() -> {ok, [{Name, Port}]} | {error, Reason} when
84      Name :: string(),
85      Port :: non_neg_integer(),
86      Reason :: address | file:posix().
87
88names() ->
89    names(localhost()).
90
91
92-spec names(Host) -> {ok, [{Name, Port}]} | {error, Reason} when
93      Host :: atom() | string() | inet:ip_address(),
94      Name :: string(),
95      Port :: non_neg_integer(),
96      Reason :: address | file:posix().
97
98names(Hostname) ->
99    ErlEpmd = net_kernel:epmd_module(),
100    ErlEpmd:names(Hostname).
101
102-spec dns_hostname(Host) -> {ok, Name} | {error, Host} when
103      Host :: atom() | string(),
104      Name :: string().
105
106dns_hostname(Hostname) ->
107    case inet:gethostbyname(Hostname) of
108	{ok,{hostent, Name, _ , _Af, _Size, _Addr}} ->
109	    {ok, Name};
110	_ ->
111	    {error, Hostname}
112    end.
113
114%% A common situation in "life" is to have a configuration file with a list
115%% of nodes, and then at startup, all nodes in the list are ping'ed
116%% this can lead to no end of troubles if two disconnected nodes
117%% simultaneously ping each other.
118%% Use this function in order to do it safely.
119%% It assumes a working global.erl which ensures a fully
120%% connected network.
121%% Had the erlang runtime system been able to fully cope with
122%% the possibility of two simultaneous (unix) connects, this function would
123%% merley  be lists:map({net_adm, ping}, [], Nodelist).
124%% It is also assumed, that the same (identical) Nodelist is given to all
125%% nodes which are to perform this call (possibly simultaneously).
126%% Even this code has a flaw, and that is the case where two
127%% nodes simultaneously and without *any* other already
128%% running nodes execute this code. :-(
129
130-spec ping_list([atom()]) -> [atom()].
131
132ping_list(Nodelist) ->
133    ok = net_kernel:monitor_nodes(true),
134    Sofar = ping_first(Nodelist, nodes()),
135    collect_new(Sofar, Nodelist).
136
137ping_first([], _S) ->
138    [];
139ping_first([Node|Nodes], S) ->
140    case lists:member(Node, S) of
141	true -> [Node | ping_first(Nodes, S)];
142	false ->
143	    case ping(Node) of
144		pong -> [Node];
145		pang -> ping_first(Nodes, S)
146	    end
147    end.
148
149collect_new(Sofar, Nodelist) ->
150    receive
151	{nodeup, Node} ->
152	    case lists:member(Node, Nodelist) of
153		true ->
154		    collect_new(Sofar, Nodelist);
155		false ->
156		    collect_new([Node | Sofar], Nodelist)
157	    end
158    after 3000 ->
159	    ok = net_kernel:monitor_nodes(false),
160	    Sofar
161    end.
162
163%% This function polls a set of hosts according to a file called
164%% .hosts.erlang that need to reside either in the current directory
165%% or in your home directory. (The current directory is tried first.)
166%% world() returns a list of all nodes on the network that can be
167%% found (including ourselves). Note: the $HOME variable is inspected.
168%%
169%% Added possibility to supply a list of hosts instead of reading
170%% the .hosts.erlang file. 971016 patrik@erix.ericsson.se
171%% e.g.
172%% net_adm:world_list(['elrond.du.etx.ericsson.se', 'thorin.du.etx.ericsson.se']).
173
174-spec world() -> [node()].
175
176world() ->
177    world(silent).
178
179-spec world(Arg) -> [node()] when
180      Arg :: verbosity().
181
182world(Verbose) ->
183    case net_adm:host_file() of
184        {error,R} -> exit({error, R});
185        Hosts -> expand_hosts(Hosts, Verbose)
186    end.
187
188-spec world_list(Hosts) -> [node()] when
189      Hosts :: [atom()].
190
191world_list(Hosts) when is_list(Hosts) ->
192    expand_hosts(Hosts, silent).
193
194-spec world_list(Hosts, Arg) -> [node()] when
195      Hosts :: [atom()],
196      Arg :: verbosity().
197
198world_list(Hosts, Verbose) when is_list(Hosts) ->
199    expand_hosts(Hosts, Verbose).
200
201expand_hosts(Hosts, Verbose) ->
202    lists:flatten(collect_nodes(Hosts, Verbose)).
203
204collect_nodes([], _) -> [];
205collect_nodes([Host|Tail], Verbose) ->
206    case collect_host_nodes(Host, Verbose) of
207        nil ->
208            collect_nodes(Tail, Verbose);
209        L ->
210            [L|collect_nodes(Tail, Verbose)]
211    end.
212
213collect_host_nodes(Host, Verbose) ->
214    case names(Host) of
215	{ok, Namelist} ->
216	    do_ping(Namelist, atom_to_list(Host), Verbose);
217	_ ->
218	    nil
219    end.
220
221do_ping(Names, Host0, Verbose) ->
222    case longshort(Host0) of
223	ignored -> [];
224	Host -> do_ping_1(Names, Host, Verbose)
225    end.
226
227do_ping_1([], _Host, _Verbose) ->
228    [];
229do_ping_1([{Name, _} | Rest], Host, Verbose) ->
230    Node = list_to_atom(Name ++ "@" ++ longshort(Host)),
231    verbose(Verbose, "Pinging ~w -> ", [Node]),
232    Result = ping(Node),
233    verbose(Verbose, "~p\n", [Result]),
234    case Result of
235	pong ->
236	    [Node | do_ping_1(Rest, Host, Verbose)];
237	pang ->
238	    do_ping_1(Rest, Host, Verbose)
239    end.
240
241verbose(verbose, Format, Args) ->
242    io:format(Format, Args);
243verbose(_, _, _) ->
244    ok.
245
246longshort(Host) ->
247    case net_kernel:longnames() of
248	false -> uptodot(Host);
249	true -> Host;
250	ignored -> ignored
251    end.
252
253uptodot([$.|_]) -> [];
254uptodot([])-> [];
255uptodot([H|T]) -> [H|uptodot(T)].
256