%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(ssh_shell).
-include("ssh.hrl").
-include("ssh_connect.hrl").
%%% As this is an user interactive client it behaves like a daemon
%%% channel inspite of it being a client.
-behaviour(ssh_server_channel).
%% ssh_server_channel callbacks
-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
%% Spawn export
-export([input_loop/2]).
-export([dbg_trace/3]).
-record(state,
{
io, %% Io process
channel, %% Id of the ssh channel
cm %% Ssh connection manager
}
).
%%====================================================================
%% ssh_server_channel callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State}
%%
%% Description: Initiates the CLI
%%--------------------------------------------------------------------
init([ConnectionManager, ChannelId] = Args) ->
%% Make sure that we are proclib compatible as
%% this client should be runnable from the
%% erlang shell.
case get('$initial_call') of
undefined ->
Me = get_my_name(),
Ancestors = get_ancestors(),
put('$ancestors', [Me | Ancestors]),
put('$initial_call', {?MODULE, init, Args});
_ ->
ok
end,
case ssh_connection:shell(ConnectionManager, ChannelId) of
ok ->
{group_leader, GIO} =
process_info(self(), group_leader),
IoPid = spawn_link(?MODULE, input_loop,
[GIO, self()]),
{ok, #state{io = IoPid,
channel = ChannelId,
cm = ConnectionManager}};
Error ->
{stop, Error}
end.
%%--------------------------------------------------------------------
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%
%% Description: Handles channel messages received on the ssh-connection.
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
io:put_chars(Data),
{ok, State};
handle_ssh_msg({ssh_cm, _,
{data, _ChannelId, ?SSH_EXTENDED_DATA_STDERR, Data}},
State) ->
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
io:put_chars(Data),
{ok, State};
handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
{ok, State};
handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
%% Ignore signals according to RFC 4254 section 6.9.
{ok, State};
handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
io:put_chars("Connection closed by peer"),
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function. The error is encoded as UTF-8!
io:put_chars(Error),
{stop, ChannelId, State};
handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
io:put_chars("logout"),
io:put_chars("Connection closed"),
{stop, ChannelId, State};
handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
io:put_chars("Connection closed by peer"),
io:put_chars("Status: " ++ integer_to_list(Status)),
{stop, ChannelId, State}.
%%--------------------------------------------------------------------
%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%
%% Description: Handles other channel messages
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
#state{channel = ChannelId,
cm = ConnectionManager} = State) ->
{ok, State};
handle_msg({input, IoPid, eof}, #state{io = IoPid, channel = ChannelId,
cm = ConnectionManager} = State) ->
ssh_connection:send_eof(ConnectionManager, ChannelId),
{ok, State};
handle_msg({input, IoPid, Line}, #state{io = IoPid,
channel = ChannelId,
cm = ConnectionManager} = State) ->
ssh_connection:send(ConnectionManager, ChannelId, Line),
{ok, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reasons, State) -> _
%%
%% Description: Cleanup when shell channel is terminated
%%--------------------------------------------------------------------
terminate(_Reason, #state{io = IoPid}) ->
exit(IoPid, kill).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
input_loop(Fd, Pid) ->
case io:get_line(Fd, '>') of
eof ->
Pid ! {input, self(), eof},
ok;
Line ->
Pid ! {input, self(), Line},
input_loop (Fd, Pid)
end.
get_my_name() ->
case process_info(self(),registered_name) of
{registered_name,Name} -> Name;
_ -> self()
end.
get_ancestors() ->
case get('$ancestors') of
A when is_list(A) -> A;
_ -> []
end.
%%%################################################################
%%%#
%%%# Tracing
%%%#
dbg_trace(points, _, _) -> [terminate];
dbg_trace(flags, terminate, _) -> [c];
dbg_trace(on, terminate, _) -> dbg:tp(?MODULE, terminate, 2, x);
dbg_trace(off, terminate, _) -> dbg:ctpg(?MODULE, terminate, 2);
dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Shell Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
].
?wr_record(state).