%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2010. 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(user). -compile( [ inline, { inline_size, 100 } ] ). %% Basic standard i/o server for user interface port. -export([start/0, start/1, start_out/0]). -export([interfaces/1]). -define(NAME, user). %% Internal exports -export([server/1, server/2]). %% Defines for control ops -define(CTRL_OP_GET_WINSIZE,100). %% %% The basic server and start-up. %% start() -> start_port([eof,binary]). start([Mod,Fun|Args]) -> %% Mod,Fun,Args should return a pid. That process is supposed to act %% as the io port. Pid = apply(Mod, Fun, Args), % This better work! Id = spawn(?MODULE, server, [Pid]), register(?NAME, Id), Id. start_out() -> %% Output-only version of start/0 start_port([out,binary]). start_port(PortSettings) -> Id = spawn(?MODULE,server,[{fd,0,1},PortSettings]), register(?NAME,Id), Id. %% Return the pid of the shell process. %% Note: We can't ask the user process for this info since it %% may be busy waiting for data from the port. interfaces(User) -> case process_info(User, dictionary) of {dictionary,Dict} -> case lists:keysearch(shell, 1, Dict) of {value,Sh={shell,Shell}} when is_pid(Shell) -> [Sh]; _ -> [] end; _ -> [] end. server(Pid) when is_pid(Pid) -> process_flag(trap_exit, true), link(Pid), run(Pid). server(PortName,PortSettings) -> process_flag(trap_exit, true), Port = open_port(PortName,PortSettings), run(Port). run(P) -> put(read_mode,list), put(unicode,false), case init:get_argument(noshell) of %% non-empty list -> noshell {ok, [_|_]} -> put(shell, noshell), server_loop(P, queue:new()); _ -> group_leader(self(), self()), catch_loop(P, start_init_shell()) end. catch_loop(Port, Shell) -> catch_loop(Port, Shell, queue:new()). catch_loop(Port, Shell, Q) -> case catch server_loop(Port, Q) of new_shell -> exit(Shell, kill), catch_loop(Port, start_new_shell()); {unknown_exit,{Shell,Reason},_} -> % shell has exited case Reason of normal -> put_chars("*** ", Port, []); _ -> put_chars("*** ERROR: ", Port, []) end, put_chars("Shell process terminated! ***\n", Port, []), catch_loop(Port, start_new_shell()); {unknown_exit,_,Q1} -> catch_loop(Port, Shell, Q1); {'EXIT',R} -> exit(R) end. link_and_save_shell(Shell) -> link(Shell), put(shell, Shell), Shell. start_init_shell() -> link_and_save_shell(shell:start(init)). start_new_shell() -> link_and_save_shell(shell:start()). server_loop(Port, Q) -> receive {io_request,From,ReplyAs,Request} when is_pid(From) -> server_loop(Port, do_io_request(Request, From, ReplyAs, Port, Q)); {Port,{data,Bytes}} -> case get(shell) of noshell -> server_loop(Port, queue:snoc(Q, Bytes)); _ -> case contains_ctrl_g_or_ctrl_c(Bytes) of false -> server_loop(Port, queue:snoc(Q, Bytes)); _ -> throw(new_shell) end end; {Port, eof} -> put(eof, true), server_loop(Port, Q); %% Ignore messages from port here. {'EXIT',Port,badsig} -> % Ignore badsig errors server_loop(Port, Q); {'EXIT',Port,What} -> % Port has exited exit(What); %% Check if shell has exited {'EXIT',SomePid,What} -> case get(shell) of noshell -> server_loop(Port, Q); % Ignore _ -> throw({unknown_exit,{SomePid,What},Q}) end; _Other -> % Ignore other messages server_loop(Port, Q) end. get_fd_geometry(Port) -> case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of List when is_list(List), length(List) =:= 8 -> <> = list_to_binary(List), {W,H}; _ -> error end. %% NewSaveBuffer = io_request(Request, FromPid, ReplyAs, Port, SaveBuffer) do_io_request(Req, From, ReplyAs, Port, Q0) -> case io_request(Req, Port, Q0) of {_Status,Reply,Q1} -> io_reply(From, ReplyAs, Reply), Q1; {exit,What} -> send_port(Port, close), exit(What) end. %% New in R13B %% Encoding option (unicode/latin1) io_request({put_chars,unicode,Chars}, Port, Q) -> % Binary new in R9C put_chars(wrap_characters_to_binary(Chars,unicode, case get(unicode) of true -> unicode; _ -> latin1 end), Port, Q); io_request({put_chars,unicode,Mod,Func,Args}, Port, Q) -> Result = case catch apply(Mod,Func,Args) of Data when is_list(Data); is_binary(Data) -> wrap_characters_to_binary(Data,unicode, case get(unicode) of true -> unicode; _ -> latin1 end); Undef -> Undef end, put_chars(Result, Port, Q); io_request({put_chars,latin1,Chars}, Port, Q) -> % Binary new in R9C Data = case get(unicode) of true -> unicode:characters_to_binary(Chars,latin1,unicode); false -> erlang:iolist_to_binary(Chars) end, put_chars(Data, Port, Q); io_request({put_chars,latin1,Mod,Func,Args}, Port, Q) -> Result = case catch apply(Mod,Func,Args) of Data when is_list(Data); is_binary(Data) -> unicode:characters_to_binary(Data,latin1, case get(unicode) of true -> unicode; _ -> latin1 end); Undef -> Undef end, put_chars(Result, Port, Q); io_request({get_chars,Enc,Prompt,N}, Port, Q) -> % New in R9C get_chars(Prompt, io_lib, collect_chars, N, Port, Q, Enc); io_request({get_line,Enc,Prompt}, Port, Q) -> case get(read_mode) of binary -> get_line_bin(Prompt,Port,Q,Enc); _ -> get_chars(Prompt, io_lib, collect_line, [], Port, Q, Enc) end; io_request({get_until,Enc,Prompt,M,F,As}, Port, Q) -> get_chars(Prompt, io_lib, get_until, {M,F,As}, Port, Q, Enc); %% End New in R13B io_request(getopts, Port, Q) -> getopts(Port, Q); io_request({setopts,Opts}, Port, Q) when is_list(Opts) -> setopts(Opts, Port, Q); io_request({requests,Reqs}, Port, Q) -> io_requests(Reqs, {ok,ok,Q}, Port); %% New in R12 io_request({get_geometry,columns},Port,Q) -> case get_fd_geometry(Port) of {W,_H} -> {ok,W,Q}; _ -> {error,{error,enotsup},Q} end; io_request({get_geometry,rows},Port,Q) -> case get_fd_geometry(Port) of {_W,H} -> {ok,H,Q}; _ -> {error,{error,enotsup},Q} end; %% BC with pre-R13 nodes io_request({put_chars,Chars}, Port, Q) -> io_request({put_chars,latin1,Chars}, Port, Q); io_request({put_chars,Mod,Func,Args}, Port, Q) -> io_request({put_chars,latin1,Mod,Func,Args}, Port, Q); io_request({get_chars,Prompt,N}, Port, Q) -> io_request({get_chars,latin1,Prompt,N}, Port, Q); io_request({get_line,Prompt}, Port, Q) -> io_request({get_line,latin1,Prompt}, Port, Q); io_request({get_until,Prompt,M,F,As}, Port, Q) -> io_request({get_until,latin1,Prompt,M,F,As}, Port, Q); io_request(R, _Port, Q) -> %Unknown request {error,{error,{request,R}},Q}. %Ignore but give error (?) %% Status = io_requests(RequestList, PrevStat, Port) %% Process a list of output requests as long as the previous status is 'ok'. io_requests([R|Rs], {ok,_Res,Q}, Port) -> io_requests(Rs, io_request(R, Port, Q), Port); io_requests([_|_], Error, _) -> Error; io_requests([], Stat, _) -> Stat. %% put_port(DeepList, Port) %% Take a deep list of characters, flatten and output them to the %% port. put_port(List, Port) -> send_port(Port, {command, List}). %% send_port(Port, Command) send_port(Port, Command) -> Port ! {self(),Command}. %% io_reply(From, ReplyAs, Reply) %% The function for sending i/o command acknowledgement. %% The ACK contains the return value. io_reply(From, ReplyAs, Reply) -> From ! {io_reply,ReplyAs,Reply}. %% put_chars put_chars(Chars, Port, Q) when is_binary(Chars) -> put_port(Chars, Port), {ok,ok,Q}; put_chars(Chars, Port, Q) -> case catch list_to_binary(Chars) of Binary when is_binary(Binary) -> put_chars(Binary, Port, Q); _ -> {error,{error,put_chars},Q} end. expand_encoding([]) -> []; expand_encoding([latin1 | T]) -> [{encoding,latin1} | expand_encoding(T)]; expand_encoding([unicode | T]) -> [{encoding,unicode} | expand_encoding(T)]; expand_encoding([H|T]) -> [H|expand_encoding(T)]. %% setopts setopts(Opts0,Port,Q) -> Opts = proplists:unfold( proplists:substitute_negations( [{list,binary}], expand_encoding(Opts0))), case check_valid_opts(Opts) of true -> do_setopts(Opts,Port,Q); false -> {error,{error,enotsup},Q} end. check_valid_opts([]) -> true; check_valid_opts([{binary,_}|T]) -> check_valid_opts(T); check_valid_opts([{encoding,Valid}|T]) when Valid =:= latin1; Valid =:= utf8; Valid =:= unicode -> check_valid_opts(T); check_valid_opts(_) -> false. do_setopts(Opts, _Port, Q) -> case proplists:get_value(encoding,Opts) of Valid when Valid =:= unicode; Valid =:= utf8 -> put(unicode,true); latin1 -> put(unicode,false); undefined -> ok end, case proplists:get_value(binary, Opts) of true -> put(read_mode,binary), {ok,ok,Q}; false -> put(read_mode,list), {ok,ok,Q}; _ -> {ok,ok,Q} end. getopts(_Port,Q) -> Bin = {binary, case get(read_mode) of binary -> true; _ -> false end}, Uni = {encoding, case get(unicode) of true -> unicode; _ -> latin1 end}, {ok,[Bin,Uni],Q}. get_line_bin(Prompt,Port,Q, Enc) -> prompt(Port, Prompt), case {get(eof),queue:is_empty(Q)} of {true,true} -> {ok,eof,Q}; _ -> get_line(Prompt,Port, Q, [], Enc) end. get_line(Prompt, Port, Q, Acc, Enc) -> case queue:is_empty(Q) of true -> receive {Port,{data,Bytes}} -> get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc); {Port, eof} -> put(eof, true), {ok, eof, []}; {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) -> do_io_request(Req, From, ReplyAs, Port, queue:new()), %% No prompt. get_line(Prompt, Port, Q, Acc, Enc); {io_request,From,ReplyAs,Request} when is_pid(From) -> do_io_request(Request, From, ReplyAs, Port, queue:new()), prompt(Port, Prompt), get_line(Prompt, Port, Q, Acc, Enc); {'EXIT',From,What} when node(From) =:= node() -> {exit,What} end; false -> get_line_doit(Prompt, Port, Q, Acc, Enc) end. get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc) -> case get(shell) of noshell -> get_line_doit(Prompt, Port, queue:snoc(Q, Bytes),Acc,Enc); _ -> case contains_ctrl_g_or_ctrl_c(Bytes) of false -> get_line_doit(Prompt, Port, queue:snoc(Q, Bytes), Acc, Enc); _ -> throw(new_shell) end end. is_cr_at(Pos,Bin) -> case Bin of <<_:Pos/binary,$\r,_/binary>> -> true; _ -> false end. srch(<<>>,_,_) -> nomatch; srch(<>,X,N) -> {match,[{N,1}]}; srch(<<_:8,T/binary>>,X,N) -> srch(T,X,N+1). get_line_doit(Prompt, Port, Q, Accu, Enc) -> case queue:is_empty(Q) of true -> case get(eof) of true -> case Accu of [] -> {ok,eof,Q}; _ -> {ok,binrev(Accu,[]),Q} end; _ -> get_line(Prompt, Port, Q, Accu, Enc) end; false -> Bin = queue:head(Q), case srch(Bin,$\n,0) of nomatch -> X = byte_size(Bin)-1, case is_cr_at(X,Bin) of true -> <> = Bin, get_line_doit(Prompt, Port, queue:tail(Q), [<<$\r>>,D|Accu], Enc); false -> get_line_doit(Prompt, Port, queue:tail(Q), [Bin|Accu], Enc) end; {match,[{Pos,1}]} -> %% We are done PosPlus = Pos + 1, case Accu of [] -> {Head,Tail} = case is_cr_at(Pos - 1,Bin) of false -> <> = Bin, {H,T}; true -> PosMinus = Pos - 1, <> = Bin, {binrev([],[H,$\n]),T} end, case Tail of <<>> -> {ok, cast(Head,Enc), queue:tail(Q)}; _ -> {ok, cast(Head,Enc), queue:cons(Tail, queue:tail(Q))} end; [<<$\r>>|Stack1] when Pos =:= 0 -> <<_:PosPlus/binary,Tail/binary>> = Bin, case Tail of <<>> -> {ok, cast(binrev(Stack1, [$\n]),Enc), queue:tail(Q)}; _ -> {ok, cast(binrev(Stack1, [$\n]),Enc), queue:cons(Tail, queue:tail(Q))} end; _ -> {Head,Tail} = case is_cr_at(Pos - 1,Bin) of false -> <> = Bin, {H,T}; true -> PosMinus = Pos - 1, <> = Bin, {[H,$\n],T} end, case Tail of <<>> -> {ok, cast(binrev(Accu,[Head]),Enc), queue:tail(Q)}; _ -> {ok, cast(binrev(Accu,[Head]),Enc), queue:cons(Tail, queue:tail(Q))} end end end end. binrev(L, T) -> list_to_binary(lists:reverse(L, T)). %% is_cr_at(Pos,Bin) -> %% case Bin of %% <<_:Pos/binary,$\r,_/binary>> -> %% true; %% _ -> %% false %% end. %% collect_line_bin_re(Bin,_Data,Stack,_) -> %% case re:run(Bin,<<"\n">>) of %% nomatch -> %% X = byte_size(Bin)-1, %% case is_cr_at(X,Bin) of %% true -> %% <> = Bin, %% [<<$\r>>,D|Stack]; %% false -> %% [Bin|Stack] %% end; %% {match,[{Pos,1}]} -> %% PosPlus = Pos + 1, %% case Stack of %% [] -> %% case is_cr_at(Pos - 1,Bin) of %% false -> %% <> = Bin, %% {stop, Head, Tail}; %% true -> %% PosMinus = Pos - 1, %% <> = Bin, %% {stop, binrev([],[Head,$\n]),Tail} %% end; %% [<<$\r>>|Stack1] when Pos =:= 0 -> %% <<_:PosPlus/binary,Tail/binary>> = Bin, %% {stop,binrev(Stack1, [$\n]),Tail}; %% _ -> %% case is_cr_at(Pos - 1,Bin) of %% false -> %% <> = Bin, %% {stop,binrev(Stack, [Head]),Tail}; %% true -> %% PosMinus = Pos - 1, %% <> = Bin, %% {stop, binrev(Stack,[Head,$\n]),Tail} %% end %% end %% end. %% get_chars(Prompt, Module, Function, XtraArg, Port, Queue) %% Gets characters from the input port until the applied function %% returns {stop,Result,RestBuf}. Does not block output until input %% has been received. %% Returns: %% {Status,Result,NewQueue} %% {exit,Reason} %% Entry function. get_chars(Prompt, M, F, Xa, Port, Q, Fmt) -> prompt(Port, Prompt), case {get(eof),queue:is_empty(Q)} of {true,true} -> {ok,eof,Q}; _ -> get_chars(Prompt, M, F, Xa, Port, Q, start, Fmt) end. %% First loop. Wait for port data. Respond to output requests. get_chars(Prompt, M, F, Xa, Port, Q, State, Fmt) -> case queue:is_empty(Q) of true -> receive {Port,{data,Bytes}} -> get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt); {Port, eof} -> put(eof, true), {ok, eof, []}; %%{io_request,From,ReplyAs,Request} when is_pid(From) -> %% get_chars_req(Prompt, M, F, Xa, Port, queue:new(), State, %% Request, From, ReplyAs); {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) -> do_io_request(Req, From, ReplyAs, Port, queue:new()), %Keep Q over this call %% No prompt. get_chars(Prompt, M, F, Xa, Port, Q, State, Fmt); {io_request,From,ReplyAs,Request} when is_pid(From) -> get_chars_req(Prompt, M, F, Xa, Port, Q, State, Request, From, ReplyAs, Fmt); {'EXIT',From,What} when node(From) =:= node() -> {exit,What} end; false -> get_chars_apply(State, M, F, Xa, Port, Q, Fmt) end. get_chars_req(Prompt, M, F, XtraArg, Port, Q, State, Req, From, ReplyAs, Fmt) -> do_io_request(Req, From, ReplyAs, Port, queue:new()), %Keep Q over this call prompt(Port, Prompt), get_chars(Prompt, M, F, XtraArg, Port, Q, State, Fmt). %% Second loop. Pass data to client as long as it wants more. %% A ^G in data interrupts loop if 'noshell' is not undefined. get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt) -> case get(shell) of noshell -> get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, Bytes),Fmt); _ -> case contains_ctrl_g_or_ctrl_c(Bytes) of false -> get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, Bytes),Fmt); _ -> throw(new_shell) end end. get_chars_apply(State0, M, F, Xa, Port, Q, Fmt) -> case catch M:F(State0, cast(queue:head(Q),Fmt), Fmt, Xa) of {stop,Result,<<>>} -> {ok,Result,queue:tail(Q)}; {stop,Result,[]} -> {ok,Result,queue:tail(Q)}; {stop,Result,eof} -> {ok,Result,queue:tail(Q)}; {stop,Result,Buf} -> {ok,Result,queue:cons(Buf, queue:tail(Q))}; {'EXIT',_} -> {error,{error,err_func(M, F, Xa)},queue:new()}; State1 -> get_chars_more(State1, M, F, Xa, Port, queue:tail(Q), Fmt) end. get_chars_more(State, M, F, Xa, Port, Q, Fmt) -> case queue:is_empty(Q) of true -> case get(eof) of undefined -> receive {Port,{data,Bytes}} -> get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt); {Port,eof} -> put(eof, true), get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, eof), Fmt); {'EXIT',From,What} when node(From) =:= node() -> {exit,What} end; _ -> get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, eof), Fmt) end; false -> get_chars_apply(State, M, F, Xa, Port, Q, Fmt) end. %% prompt(Port, Prompt) %% Print Prompt onto Port %% common case, reduces execution time by 20% prompt(_Port, '') -> ok; prompt(Port, Prompt) -> put_port(wrap_characters_to_binary(io_lib:format_prompt(Prompt),unicode, case get(unicode) of true -> unicode; _ -> latin1 end), Port). %% Convert error code to make it look as before err_func(io_lib, get_until, {_,F,_}) -> F; err_func(_, F, _) -> F. %% using regexp reduces execution time by >50% compared to old code %% running two regexps in sequence is much faster than \\x03|\\x07 contains_ctrl_g_or_ctrl_c(BinOrList)-> case {re:run(BinOrList, <<3>>),re:run(BinOrList, <<7>>)} of {nomatch, nomatch} -> false; _ -> true end. %% Convert a buffer between list and binary cast(Data, _Format) when is_atom(Data) -> Data; cast(Data, Format) -> cast(Data, get(read_mode), Format, get(unicode)). cast(B, binary, latin1, false) when is_binary(B) -> B; cast(B, binary, latin1, true) when is_binary(B) -> unicode:characters_to_binary(B, unicode, latin1); cast(L, binary, latin1, false) -> erlang:iolist_to_binary(L); cast(L, binary, latin1, true) -> case unicode:characters_to_binary( erlang:iolist_to_binary(L),unicode,latin1) of % may fail {error,_,_} -> exit({no_translation, unicode, latin1}); Else -> Else end; cast(B, binary, unicode, true) when is_binary(B) -> B; cast(B, binary, unicode, false) when is_binary(B) -> unicode:characters_to_binary(B,latin1,unicode); cast(L, binary, unicode, true) -> % possibly a list containing UTF-8 encoded characters unicode:characters_to_binary(erlang:iolist_to_binary(L)); cast(L, binary, unicode, false) -> unicode:characters_to_binary(L, latin1, unicode); cast(L, list, latin1, UniTerm) -> case UniTerm of true -> % Convert input characters to protocol format (i.e latin1) case unicode:characters_to_list( erlang:iolist_to_binary(L),unicode) of % may fail {error,_,_} -> exit({no_translation, unicode, latin1}); Else -> [ case X of High when High > 255 -> exit({no_translation, unicode, latin1}); Low -> Low end || X <- Else ] end; _ -> binary_to_list(erlang:iolist_to_binary(L)) end; cast(L, list, unicode, UniTerm) -> unicode:characters_to_list(erlang:iolist_to_binary(L), case UniTerm of true -> unicode; _ -> latin1 end); cast(Other, _, _,_) -> Other. wrap_characters_to_binary(Chars,unicode,latin1) -> case unicode:characters_to_binary(Chars,unicode,latin1) of {error,_,_} -> list_to_binary( [ case X of High when High > 255 -> ["\\x{",erlang:integer_to_list(X, 16),$}]; Low -> Low end || X <- unicode:characters_to_list(Chars,unicode) ]); Bin -> Bin end; wrap_characters_to_binary(Bin,From,From) when is_binary(Bin) -> Bin; wrap_characters_to_binary(Chars,From,To) -> unicode:characters_to_binary(Chars,From,To).