aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/erl_boot_server.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/src/erl_boot_server.erl')
-rw-r--r--lib/kernel/src/erl_boot_server.erl325
1 files changed, 325 insertions, 0 deletions
diff --git a/lib/kernel/src/erl_boot_server.erl b/lib/kernel/src/erl_boot_server.erl
new file mode 100644
index 0000000000..702b2feac9
--- /dev/null
+++ b/lib/kernel/src/erl_boot_server.erl
@@ -0,0 +1,325 @@
+%%
+%% %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%
+%%
+%% A simple boot_server at a CP.
+%%
+%% This server should know about which slaves (DP's or whatever) to boot.
+%% File's (with absolute path name) will be fetched.
+%%
+
+-module(erl_boot_server).
+
+-include("inet_boot.hrl").
+
+-behaviour(gen_server).
+
+%% API functions.
+-export([start/1, start_link/1, add_slave/1, delete_slave/1,
+ add_subnet/2, delete_subnet/2,
+ which_slaves/0]).
+
+%% Exports for testing (dont't remove; tests suites depend on them).
+-export([would_be_booted/1]).
+
+%% Internal exports
+-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2]).
+-export([code_change/3]).
+-export([boot_init/1, boot_accept/3]).
+
+-record(state,
+ {
+ priority = 0, %% priority of this server
+ version = "" :: string(), %% Version handled i.e "4.5.3" etc
+ udp_sock, %% listen port for broadcase requests
+ udp_port, %% port number must be ?EBOOT_PORT!
+ listen_sock, %% listen sock for incoming file requests
+ listen_port, %% listen port number
+ slaves, %% list of accepted ip addresses
+ bootp :: pid(), %% boot process
+ prim_state %% state for efile code loader
+ }).
+
+-define(single_addr_mask, {255, 255, 255, 255}).
+
+-type ip4_address() :: {0..255,0..255,0..255,0..255}.
+
+-spec start(Slaves :: [atom()]) -> {'ok', pid()} | {'error', any()}.
+
+start(Slaves) ->
+ case check_arg(Slaves) of
+ {ok, AL} ->
+ gen_server:start({local,boot_server}, erl_boot_server, AL, []);
+ _ ->
+ {error, {badarg, Slaves}}
+ end.
+
+-spec start_link(Slaves :: [atom()]) -> {'ok', pid()} | {'error', any()}.
+
+start_link(Slaves) ->
+ case check_arg(Slaves) of
+ {ok, AL} ->
+ gen_server:start_link({local,boot_server},
+ erl_boot_server, AL, []);
+ _ ->
+ {error, {badarg, Slaves}}
+ end.
+
+check_arg(Slaves) ->
+ check_arg(Slaves, []).
+
+check_arg([Slave|Rest], Result) ->
+ case inet:getaddr(Slave, inet) of
+ {ok, IP} ->
+ check_arg(Rest, [{?single_addr_mask, IP}|Result]);
+ _ ->
+ error
+ end;
+check_arg([], Result) ->
+ {ok, Result};
+check_arg(_, _Result) ->
+ error.
+
+-spec add_slave(Slave :: atom()) -> 'ok' | {'error', any()}.
+
+add_slave(Slave) ->
+ case inet:getaddr(Slave, inet) of
+ {ok,IP} ->
+ gen_server:call(boot_server, {add, {?single_addr_mask, IP}});
+ _ ->
+ {error, {badarg, Slave}}
+ end.
+
+-spec delete_slave(Slave :: atom()) -> 'ok' | {'error', any()}.
+
+delete_slave(Slave) ->
+ case inet:getaddr(Slave, inet) of
+ {ok,IP} ->
+ gen_server:call(boot_server, {delete, {?single_addr_mask, IP}});
+ _ ->
+ {error, {badarg, Slave}}
+ end.
+
+-spec add_subnet(Mask :: ip4_address(), Addr :: ip4_address()) ->
+ 'ok' | {'error', any()}.
+
+add_subnet(Mask, Addr) when is_tuple(Mask), is_tuple(Addr) ->
+ case member_address(Addr, [{Mask, Addr}]) of
+ true ->
+ gen_server:call(boot_server, {add, {Mask, Addr}});
+ false ->
+ {error, empty_subnet}
+ end.
+
+-spec delete_subnet(Mask :: ip4_address(), Addr :: ip4_address()) -> 'ok'.
+
+delete_subnet(Mask, Addr) when is_tuple(Mask), is_tuple(Addr) ->
+ gen_server:call(boot_server, {delete, {Mask, Addr}}).
+
+-spec which_slaves() -> [atom()].
+
+which_slaves() ->
+ gen_server:call(boot_server, which).
+
+%% Given a host name or IP address, returns true if a host
+%% having that IP address would be accepted for booting, and
+%% false otherwise. (Convenient for testing.)
+
+would_be_booted(Addr) ->
+ {ok, IP} = inet:getaddr(Addr, inet),
+ member_address(IP, which_slaves()).
+
+int16(X) when is_integer(X) ->
+ [(X bsr 8) band 16#ff, (X) band 16#ff].
+
+%% Check if an address is a member
+
+member_address(IP, [{{MA, MB, MC, MD}, {EA, EB, EC, ED}}|Rest]) ->
+ {A, B, C, D} = IP,
+ if A band MA =:= EA,
+ B band MB =:= EB,
+ C band MC =:= EC,
+ D band MD =:= ED ->
+ true;
+ true ->
+ member_address(IP, Rest)
+ end;
+member_address(_, []) ->
+ false.
+
+%% ------------------------------------------------------------
+%% call-back functions.
+%% ------------------------------------------------------------
+
+init(Slaves) ->
+ {ok, U} = gen_udp:open(?EBOOT_PORT, []),
+ {ok, L} = gen_tcp:listen(0, [binary,{packet,4}]),
+ {ok, Port} = inet:port(L),
+ {ok, UPort} = inet:port(U),
+ Ref = make_ref(),
+ Pid = proc_lib:spawn_link(?MODULE, boot_init, [Ref]),
+ gen_tcp:controlling_process(L, Pid),
+ Pid ! {Ref, L},
+ %% We trap exit inorder to restart boot_init and udp_port
+ process_flag(trap_exit, true),
+ {ok, #state {priority = 0,
+ version = erlang:system_info(version),
+ udp_sock = U,
+ udp_port = UPort,
+ listen_sock = L,
+ listen_port = Port,
+ slaves = ordsets:from_list(Slaves),
+ bootp = Pid
+ }}.
+
+handle_call({add,Address}, _, S0) ->
+ Slaves = ordsets:add_element(Address, S0#state.slaves),
+ S0#state.bootp ! {slaves, Slaves},
+ {reply, ok, S0#state{slaves = Slaves}};
+handle_call({delete,Address}, _, S0) ->
+ Slaves = ordsets:del_element(Address, S0#state.slaves),
+ S0#state.bootp ! {slaves, Slaves},
+ {reply, ok, S0#state{slaves = Slaves}};
+handle_call(which, _, S0) ->
+ {reply, ordsets:to_list(S0#state.slaves), S0}.
+
+handle_cast(_, Slaves) ->
+ {noreply, Slaves}.
+
+handle_info({udp, U, IP, Port, Data}, S0) ->
+ Token = ?EBOOT_REQUEST ++ S0#state.version,
+ Valid = member_address(IP, ordsets:to_list(S0#state.slaves)),
+ %% check that the connecting node is valid and has the same
+ %% erlang version as the boot server node
+ case {Valid,Data,Token} of
+ {true,Token,Token} ->
+ gen_udp:send(U,IP,Port,[?EBOOT_REPLY,S0#state.priority,
+ int16(S0#state.listen_port),
+ S0#state.version]),
+ {noreply,S0};
+ {false,_,_} ->
+ error_logger:error_msg("** Illegal boot server connection attempt: "
+ "~w is not a valid address ** ~n", [IP]),
+ {noreply,S0};
+ {true,_,_} ->
+ case catch string:substr(Data, 1, length(?EBOOT_REQUEST)) of
+ ?EBOOT_REQUEST ->
+ Vsn = string:substr(Data, length(?EBOOT_REQUEST)+1, length(Data)),
+ error_logger:error_msg("** Illegal boot server connection attempt: "
+ "client version is ~s ** ~n", [Vsn]);
+ _ ->
+ error_logger:error_msg("** Illegal boot server connection attempt: "
+ "unrecognizable request ** ~n", [])
+ end,
+ {noreply,S0}
+ end;
+handle_info(_Info, S0) ->
+ {noreply,S0}.
+
+terminate(_Reason, _S0) ->
+ ok.
+
+code_change(_Vsn, State, _Extra) ->
+ {ok, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Boot server
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+boot_init(Tag) ->
+ receive
+ {Tag, Listen} ->
+ process_flag(trap_exit, true),
+ boot_main(Listen)
+ end.
+
+boot_main(Listen) ->
+ Tag = make_ref(),
+ Pid = proc_lib:spawn_link(?MODULE, boot_accept, [self(), Listen, Tag]),
+ boot_main(Listen, Tag, Pid).
+
+boot_main(Listen, Tag, Pid) ->
+ receive
+ {Tag, _} ->
+ boot_main(Listen);
+ {'EXIT', Pid, _} ->
+ boot_main(Listen);
+ {'EXIT', _, Reason} ->
+ exit(Pid, kill),
+ exit(Reason);
+ {tcp_closed, Listen} ->
+ exit(closed)
+ end.
+
+boot_accept(Server, Listen, Tag) ->
+ Reply = gen_tcp:accept(Listen),
+ unlink(Server),
+ Server ! {Tag, continue},
+ case Reply of
+ {ok, Socket} ->
+ {ok, {IP, _Port}} = inet:peername(Socket),
+ true = member_address(IP, which_slaves()),
+ PS = erl_prim_loader:prim_init(),
+ boot_loop(Socket, PS)
+ end.
+
+boot_loop(Socket, PS) ->
+ receive
+ {tcp, Socket, Data} ->
+ PS2 = handle_command(Socket, PS, Data),
+ boot_loop(Socket, PS2);
+ {tcp_closed, Socket} ->
+ true
+ end.
+
+handle_command(S, PS, Msg) ->
+ case catch binary_to_term(Msg) of
+ {get,File} ->
+ {Res, PS2} = erl_prim_loader:prim_get_file(PS, File),
+ send_file_result(S, get, Res),
+ PS2;
+ {list_dir,Dir} ->
+ {Res, PS2} = erl_prim_loader:prim_list_dir(PS, Dir),
+ send_file_result(S, list_dir, Res),
+ PS2;
+ {read_file_info,File} ->
+ {Res, PS2} = erl_prim_loader:prim_read_file_info(PS, File),
+ send_file_result(S, read_file_info, Res),
+ PS2;
+ get_cwd ->
+ {Res, PS2} = erl_prim_loader:prim_get_cwd(PS, []),
+ send_file_result(S, get_cwd, Res),
+ PS2;
+ {get_cwd,Drive} ->
+ {Res, PS2} = erl_prim_loader:prim_get_cwd(PS, [Drive]),
+ send_file_result(S, get_cwd, Res),
+ PS2;
+ {'EXIT',Reason} ->
+ send_result(S, {error,Reason}),
+ PS;
+ _Other ->
+ send_result(S, {error,unknown_command}),
+ PS
+ end.
+
+send_file_result(S, Cmd, Result) ->
+ gen_tcp:send(S, term_to_binary({Cmd,Result})).
+
+send_result(S, Result) ->
+ gen_tcp:send(S, term_to_binary(Result)).