1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2003-2020. 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 21-module(ct_telnet). 22 23-export([open/1, open/2, open/3, open/4, close/1]). 24-export([cmd/2, cmd/3, cmdf/3, cmdf/4, get_data/1, 25 send/2, send/3, sendf/3, sendf/4, 26 expect/2, expect/3]). 27 28%% Callbacks 29-export([init/3,handle_msg/2,reconnect/2,terminate/2]). 30 31%% Tool internals 32-export([silent_teln_expect/6, teln_receive_until_prompt/3, 33 format_data/2]). 34-export([start_gen_log/1, end_gen_log/0, log/3, log/4]). 35 36-define(RECONNS,3). 37-define(RECONN_TIMEOUT,5000). 38-define(DEFAULT_TIMEOUT,10000). 39-define(DEFAULT_PORT,23). 40-define(POLL_LIMIT,0). 41-define(POLL_INTERVAL,1000). 42 43-include("ct_util.hrl"). 44 45-record(state,{host, 46 port, 47 teln_pid, 48 prx, 49 buffer=[], 50 prompt=false, 51 name, 52 type, 53 target_mod, 54 keep_alive, 55 poll_limit=?POLL_LIMIT, 56 poll_interval=?POLL_INTERVAL, 57 extra, 58 conn_to=?DEFAULT_TIMEOUT, 59 com_to=?DEFAULT_TIMEOUT, 60 reconns=?RECONNS, 61 reconn_int=?RECONN_TIMEOUT, 62 tcp_nodelay=false}). 63 64open(Name) -> 65 open(Name,telnet). 66 67open(Name,ConnType) -> 68 case ct_util:get_key_from_name(Name) of 69 {ok, unix} -> % unix host 70 open(Name, ConnType, unix_telnet, Name); 71 {ok, Key} -> % any other, e.g. interwatch (iw), etc. 72 open(Name, ConnType, Key, Name); 73 Error -> 74 Error 75 end. 76 77open(KeyOrName,ConnType,TargetMod) -> 78 open(KeyOrName,ConnType,TargetMod,KeyOrName). 79 80open(KeyOrName,ConnType,TargetMod,Extra) -> 81 case ct:get_config({KeyOrName,ConnType}) of 82 undefined -> 83 log(undefined,open,"Failed: ~tp",[{not_available,KeyOrName}]), 84 {error,{not_available,KeyOrName,ConnType}}; 85 Addr -> 86 Addr1 = 87 case Addr of 88 {_IP,_Port} -> 89 Addr; 90 IP -> 91 case ct:get_config({KeyOrName,port}) of 92 undefined -> IP; 93 P -> {IP,P} 94 end 95 end, 96 KeepAlive = 97 case ct:get_config({KeyOrName,keep_alive}) of 98 undefined -> 99 case ct:get_config({telnet_settings,keep_alive}) of 100 undefined -> true; 101 Bool -> Bool 102 end; 103 Bool -> Bool 104 end, 105 log(undefined,open,"Connecting to ~tp(~tp)", 106 [KeyOrName,Addr1]), 107 Reconnect = 108 case ct:get_config({telnet_settings,reconnection_attempts}) of 109 0 -> false; 110 _ -> true 111 end, 112 ct_gen_conn:start(full_addr(Addr1,ConnType), 113 {TargetMod,KeepAlive,Extra}, 114 ?MODULE, [{name,KeyOrName}, 115 {reconnect,Reconnect}, 116 {old,true}]) 117 end. 118 119close(Connection) -> 120 case get_handle(Connection) of 121 {ok,Pid} -> 122 log(undefined,close,"Connection closed, handle: ~w",[Pid]), 123 case ct_gen_conn:stop(Pid) of 124 {error,{process_down,Pid,_}} -> 125 {error,already_closed}; 126 Result -> 127 Result 128 end; 129 Error -> 130 Error 131 end. 132 133%%%================================================================= 134%%% Test suite interface 135%%%----------------------------------------------------------------- 136 137cmd(Connection,Cmd) -> 138 cmd(Connection,Cmd,[]). 139 140cmd(Connection,Cmd,Opts) when is_list(Opts) -> 141 case check_cmd_opts(Opts) of 142 ok -> 143 case get_handle(Connection) of 144 {ok,Pid} -> 145 call(Pid,{cmd,Cmd,Opts}); 146 Error -> 147 Error 148 end; 149 Error -> 150 Error 151 end; 152cmd(Connection,Cmd,Timeout) when is_integer(Timeout); Timeout==default -> 153 %% This clause is kept for backwards compatibility only 154 cmd(Connection,Cmd,[{timeout,Timeout}]). 155 156check_cmd_opts([{timeout,Timeout}|Opts]) when is_integer(Timeout); 157 Timeout==default -> 158 check_cmd_opts(Opts); 159check_cmd_opts([]) -> 160 ok; 161check_cmd_opts(Opts) -> 162 check_send_opts(Opts). 163 164cmdf(Connection,CmdFormat,Args) -> 165 cmdf(Connection,CmdFormat,Args,[]). 166 167cmdf(Connection,CmdFormat,Args,Opts) when is_list(Args) -> 168 Cmd = lists:flatten(io_lib:format(CmdFormat,Args)), 169 cmd(Connection,Cmd,Opts). 170 171get_data(Connection) -> 172 case get_handle(Connection) of 173 {ok,Pid} -> 174 call(Pid,get_data); 175 Error -> 176 Error 177 end. 178 179send(Connection,Cmd) -> 180 send(Connection,Cmd,[]). 181 182send(Connection,Cmd,Opts) -> 183 case check_send_opts(Opts) of 184 ok -> 185 case get_handle(Connection) of 186 {ok,Pid} -> 187 call(Pid,{send,Cmd,Opts}); 188 Error -> 189 Error 190 end; 191 Error -> 192 Error 193 end. 194 195check_send_opts([{newline,Bool}|Opts]) when is_boolean(Bool) -> 196 check_send_opts(Opts); 197check_send_opts([{newline,String}|Opts]) when is_list(String) -> 198 case lists:all(fun(I) when is_integer(I), I>=0, I=<127 -> true; 199 (_) -> false 200 end, String) of 201 true -> 202 check_send_opts(Opts); 203 false -> 204 {error,{invalid_option,{newline,String}}} 205 end; 206check_send_opts([Invalid|_]) -> 207 {error,{invalid_option,Invalid}}; 208check_send_opts([]) -> 209 ok. 210 211sendf(Connection,CmdFormat,Args) when is_list(Args) -> 212 sendf(Connection,CmdFormat,Args,[]). 213 214sendf(Connection,CmdFormat,Args,Opts) when is_list(Args) -> 215 Cmd = lists:flatten(io_lib:format(CmdFormat,Args)), 216 send(Connection,Cmd,Opts). 217 218expect(Connection,Patterns) -> 219 expect(Connection,Patterns,[]). 220 221expect(Connection,Patterns,Opts) -> 222 case get_handle(Connection) of 223 {ok,Pid} -> 224 case call(Pid,{expect,Patterns,Opts}) of 225 {error,Reason} when element(1,Reason)==bad_pattern -> 226 %% Faulty user input - should fail the test case 227 exit({Reason,{?MODULE,?FUNCTION_NAME,3}}); 228 Other -> 229 Other 230 end; 231 Error -> 232 Error 233 end. 234 235%%%================================================================= 236%%% Callback functions 237 238init(Name,{Ip,Port,Type},{TargetMod,KeepAlive,Extra}) -> 239 S0 = case ct:get_config(telnet_settings) of 240 undefined -> 241 #state{}; 242 Settings -> 243 set_telnet_defaults(Settings,#state{}) 244 end, 245 %% Handle old user versions of TargetMod 246 _ = code:ensure_loaded(TargetMod), 247 try 248 case erlang:function_exported(TargetMod,connect,7) of 249 true -> 250 TargetMod:connect(Name,Ip,Port,S0#state.conn_to, 251 KeepAlive,S0#state.tcp_nodelay,Extra); 252 false -> 253 TargetMod:connect(Name,Ip,Port,S0#state.conn_to, 254 KeepAlive,Extra) 255 end 256 of 257 {ok,TelnPid} -> 258 put({ct_telnet_pid2name,TelnPid},Name), 259 S1 = S0#state{host=Ip, 260 port=Port, 261 teln_pid=TelnPid, 262 name=Name, 263 type=type(Type), 264 target_mod=TargetMod, 265 keep_alive=KeepAlive, 266 extra=Extra, 267 prx=TargetMod:get_prompt_regexp()}, 268 log(S1,open, 269 "Opened telnet connection\n" 270 "IP: ~p\n" 271 "Port: ~p\n" 272 "Command timeout: ~p\n" 273 "Reconnection attempts: ~p\n" 274 "Reconnection interval: ~p\n" 275 "Connection timeout: ~p\n" 276 "Keep alive: ~w\n" 277 "Poll limit: ~w\n" 278 "Poll interval: ~w\n" 279 "TCP nodelay: ~w", 280 [Ip,Port,S1#state.com_to,S1#state.reconns, 281 S1#state.reconn_int,S1#state.conn_to,KeepAlive, 282 S1#state.poll_limit,S1#state.poll_interval, 283 S1#state.tcp_nodelay]), 284 {ok,TelnPid,S1}; 285 Error -> 286 Error 287 catch 288 _:Reason -> 289 {error,Reason} 290 end. 291 292type(telnet) -> ip; 293type(TS) when TS==ts1;TS==ts2 -> ts. 294 295set_telnet_defaults([{connect_timeout,CnTo}|Ss],S) -> 296 set_telnet_defaults(Ss,S#state{conn_to=CnTo}); 297set_telnet_defaults([{command_timeout,CmTo}|Ss],S) -> 298 set_telnet_defaults(Ss,S#state{com_to=CmTo}); 299set_telnet_defaults([{reconnection_attempts,Rs}|Ss],S) -> 300 set_telnet_defaults(Ss,S#state{reconns=Rs}); 301set_telnet_defaults([{reconnection_interval,RInt}|Ss],S) -> 302 set_telnet_defaults(Ss,S#state{reconn_int=RInt}); 303set_telnet_defaults([{keep_alive,_}|Ss],S) -> 304 set_telnet_defaults(Ss,S); 305set_telnet_defaults([{poll_limit,PL}|Ss],S) -> 306 set_telnet_defaults(Ss,S#state{poll_limit=PL}); 307set_telnet_defaults([{poll_interval,PI}|Ss],S) -> 308 set_telnet_defaults(Ss,S#state{poll_interval=PI}); 309set_telnet_defaults([{tcp_nodelay,NoDelay}|Ss],S) -> 310 set_telnet_defaults(Ss,S#state{tcp_nodelay=NoDelay}); 311set_telnet_defaults([Unknown|Ss],S) -> 312 force_log(S,error, 313 "Bad element in telnet_settings: ~tp",[Unknown]), 314 set_telnet_defaults(Ss,S); 315set_telnet_defaults([],S) -> 316 S. 317 318handle_msg({cmd,Cmd,Opts},State) -> 319 start_gen_log(heading(cmd,State#state.name)), 320 log(State,cmd,"Cmd: ~tp",[Cmd]), 321 322 %% whatever is in the buffer from previous operations 323 %% will be ignored as we go ahead with this telnet cmd 324 325 debug_cont_gen_log("Throwing Buffer:",[]), 326 debug_log_lines(State#state.buffer), 327 328 _ = case {State#state.type,State#state.prompt} of 329 {ts,_} -> 330 silent_teln_expect(State#state.name, 331 State#state.teln_pid, 332 State#state.buffer, 333 prompt, 334 State#state.prx, 335 [{idle_timeout,2000}]); 336 {ip,false} -> 337 silent_teln_expect(State#state.name, 338 State#state.teln_pid, 339 State#state.buffer, 340 prompt, 341 State#state.prx, 342 [{idle_timeout,200}]); 343 {ip,true} -> 344 ok 345 end, 346 TO = case proplists:get_value(timeout,Opts,default) of 347 default -> State#state.com_to; 348 Timeout -> Timeout 349 end, 350 Newline = proplists:get_value(newline,Opts,true), 351 {Return,NewBuffer,Prompt} = 352 case teln_cmd(State#state.teln_pid, Cmd, State#state.prx, 353 Newline, TO) of 354 {ok,Data,_PromptType,Rest} -> 355 log(State,recv,"Return: ~tp",[{ok,Data}]), 356 {{ok,Data},Rest,true}; 357 Error -> 358 Retry = {retry,{Error, 359 {State#state.name, 360 State#state.type}, 361 State#state.teln_pid, 362 {cmd,Cmd,Opts}}}, 363 log(State,recv,"Return: ~tp",[Error]), 364 {Retry,[],false} 365 end, 366 end_gen_log(), 367 {Return,State#state{buffer=NewBuffer,prompt=Prompt}}; 368handle_msg({send,Cmd,Opts},State) -> 369 start_gen_log(heading(send,State#state.name)), 370 log(State,send,"Sending: ~tp",[Cmd]), 371 372 debug_cont_gen_log("Throwing Buffer:",[]), 373 debug_log_lines(State#state.buffer), 374 375 _ = case {State#state.type,State#state.prompt} of 376 {ts,_} -> 377 silent_teln_expect(State#state.name, 378 State#state.teln_pid, 379 State#state.buffer, 380 prompt, 381 State#state.prx, 382 [{idle_timeout,2000}]); 383 {ip,false} -> 384 silent_teln_expect(State#state.name, 385 State#state.teln_pid, 386 State#state.buffer, 387 prompt, 388 State#state.prx, 389 [{idle_timeout,200}]); 390 {ip,true} -> 391 ok 392 end, 393 Newline = proplists:get_value(newline,Opts,true), 394 ct_telnet_client:send_data(State#state.teln_pid,Cmd,Newline), 395 end_gen_log(), 396 {ok,State#state{buffer=[],prompt=false}}; 397handle_msg(get_data,State) -> 398 start_gen_log(heading(get_data,State#state.name)), 399 log(State,cmd,"Reading data...",[]), 400 {ok,Data,Buffer} = teln_get_all_data(State,State#state.buffer,[],[], 401 State#state.poll_limit), 402 log(State,recv,"Return: ~tp",[{ok,Data}]), 403 end_gen_log(), 404 {{ok,Data},State#state{buffer=Buffer}}; 405handle_msg({expect,Pattern,Opts},State) -> 406 start_gen_log(heading(expect,State#state.name)), 407 log(State,expect,"Expect: ~tp\nOpts = ~tp\n",[Pattern,Opts]), 408 {Return,NewBuffer,Prompt} = 409 case teln_expect(State#state.name, 410 State#state.teln_pid, 411 State#state.buffer, 412 Pattern, 413 State#state.prx, 414 Opts) of 415 {ok,Data,Rest} -> 416 P = check_if_prompt_was_reached(Data,[]), 417 {{ok,Data},Rest,P}; 418 {ok,Data,HaltReason,Rest} -> 419 force_log(State,expect,"HaltReason: ~tp",[HaltReason]), 420 P = check_if_prompt_was_reached(Data,HaltReason), 421 {{ok,Data,HaltReason},Rest,P}; 422 {error,Reason,Rest} -> 423 force_log(State,expect,"Expect failed\n~tp",[{error,Reason}]), 424 P = check_if_prompt_was_reached([],Reason), 425 {{error,Reason},Rest,P}; 426 {error,Reason} -> 427 force_log(State,expect,"Expect failed\n~tp",[{error,Reason}]), 428 P = check_if_prompt_was_reached([],Reason), 429 {{error,Reason},[],P} 430 end, 431 end_gen_log(), 432 Return1 = case Return of 433 {error,_} -> {retry,{Return, 434 {State#state.name, 435 State#state.type}, 436 State#state.teln_pid, 437 {expect,Pattern,Opts}}}; 438 _ -> Return 439 end, 440 {Return1,State#state{buffer=NewBuffer,prompt=Prompt}}. 441 442 443reconnect({Ip,Port,_Type},State) -> 444 reconnect(Ip,Port,State#state.reconns,State). 445reconnect(Ip,Port,N,State=#state{name=Name, 446 target_mod=TargetMod, 447 keep_alive=KeepAlive, 448 extra=Extra, 449 conn_to=ConnTo, 450 reconn_int=ReconnInt, 451 tcp_nodelay=NoDelay}) -> 452 %% Handle old user versions of TargetMod 453 ConnResult = 454 case erlang:function_exported(TargetMod,connect,7) of 455 true -> 456 TargetMod:connect(Name,Ip,Port,ConnTo,KeepAlive,NoDelay,Extra); 457 false -> 458 TargetMod:connect(Name,Ip,Port,ConnTo,KeepAlive,Extra) 459 end, 460 case ConnResult of 461 {ok,NewPid} -> 462 put({ct_telnet_pid2name,NewPid},Name), 463 {ok, NewPid, State#state{teln_pid=NewPid}}; 464 Error when N==0 -> 465 Error; 466 _Error -> 467 log(State,reconnect,"Reconnect failed!","Retries left: ~w",[N]), 468 timer:sleep(ReconnInt), 469 reconnect(Ip,Port,N-1,State) 470 end. 471 472 473terminate(TelnPid,State) -> 474 Result = ct_telnet_client:close(TelnPid), 475 log(State,close,"Telnet connection for ~w closed.",[TelnPid]), 476 Result. 477 478%%%================================================================= 479%%% Internal function 480get_handle(Pid) when is_pid(Pid) -> 481 {ok,Pid}; 482get_handle({Name,Type}) when Type==telnet;Type==ts1;Type==ts2 -> 483 case ct_util:get_connection(Name,?MODULE) of 484 {ok,Conn} -> 485 case get_handle(Type,Conn) of 486 {ok,Pid} -> 487 {ok,Pid}; 488 _Error -> 489 case ct_util:get_key_from_name(Name) of 490 {ok,node} -> 491 open(Name,Type,ct_telnet_cello_node); 492 {ok,unix} -> % unix host 493 open(Name,Type,unix_telnet,Name); 494 {ok,Key} -> % any other, e.g. interwatch (iw) 495 open(Name,Type,Key,Name); 496 Error -> 497 Error 498 end 499 end; 500 Error -> 501 Error 502 end; 503get_handle(Name) -> 504 get_handle({Name,telnet}). 505 506get_handle(Type,{Pid,{_,_,Type}}) -> 507 {ok,Pid}; 508get_handle(Type,_) -> 509 {error,{no_such_connection,Type}}. 510 511full_addr({Ip,Port},Type) -> 512 {Ip,Port,Type}; 513full_addr(Ip,Type) -> 514 {Ip,?DEFAULT_PORT,Type}. 515 516call(Pid,Msg) -> 517 ct_gen_conn:call(Pid,Msg). 518 519check_if_prompt_was_reached({prompt,_},_) -> 520 true; 521check_if_prompt_was_reached(_,{prompt,_}) -> 522 true; 523check_if_prompt_was_reached(Data,_) when is_list(Data) -> 524 lists:keymember(prompt,1,Data); 525check_if_prompt_was_reached(_,_) -> 526 false. 527 528%%%----------------------------------------------------------------- 529%%% Functions for logging ct_telnet reports and telnet data 530 531heading(Action,undefined) -> 532 io_lib:format("~w ~w",[?MODULE,Action]); 533heading(Action,Name) -> 534 io_lib:format("~w ~w for ~tp",[?MODULE,Action,Name]). 535 536force_log(State,Action,String,Args) -> 537 log(State,Action,String,Args,true). 538 539%%%----------------------------------------------------------------- 540log(State,Action,String,Args) when is_record(State, state) -> 541 log(State,Action,String,Args,false); 542log(Name,Action,String,Args) when is_atom(Name) -> 543 log(#state{name=Name},Action,String,Args,false); 544log(TelnPid,Action,String,Args) when is_pid(TelnPid) -> 545 log(#state{teln_pid=TelnPid},Action,String,Args,false). 546 547%%%----------------------------------------------------------------- 548log(undefined,String,Args) -> 549 log(#state{},undefined,String,Args,false); 550log(Name,String,Args) when is_atom(Name) -> 551 log(#state{name=Name},undefined,String,Args,false); 552log(TelnPid,String,Args) when is_pid(TelnPid) -> 553 log(#state{teln_pid=TelnPid},undefined,String,Args). 554 555%%%----------------------------------------------------------------- 556log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port}, 557 Action,String,Args,ForcePrint) -> 558 Name1 = if Name == undefined -> get({ct_telnet_pid2name,TelnPid}); 559 true -> Name 560 end, 561 Silent = get(silent), 562 563 if Action == general_io -> 564 case ct_util:get_testdata({cth_conn_log,?MODULE}) of 565 HookMode when HookMode /= undefined, HookMode /= silent, 566 Silent /= true -> 567 error_logger:info_report(#conn_log{header=false, 568 client=self(), 569 conn_pid=TelnPid, 570 address={Host,Port}, 571 name=Name1, 572 action=Action, 573 module=?MODULE}, 574 {String,Args}); 575 _ -> %% hook inactive or silence requested 576 ok 577 end; 578 579 true -> 580 if Action == open; Action == close; Action == reconnect; 581 Action == info; Action == error -> 582 ct_gen_conn:log(heading(Action,Name1),String,Args); 583 584 ForcePrint == false -> 585 case ct_util:is_silenced(telnet) of 586 true -> 587 ok; 588 false -> 589 ct_gen_conn:cont_log_no_timestamp(String,Args) 590 end; 591 592 ForcePrint == true -> 593 case ct_util:is_silenced(telnet) of 594 true -> 595 %% call log/3 now instead of cont_log/2 since 596 %% start_gen_log/1 will not have been previously 597 %% called 598 ct_gen_conn:log(heading(Action,Name1),String,Args); 599 false -> 600 ct_gen_conn:cont_log_no_timestamp(String,Args) 601 end 602 end 603 end. 604 605%%%----------------------------------------------------------------- 606start_gen_log(Heading) -> 607 %% check if output is suppressed 608 case ct_util:is_silenced(telnet) of 609 true -> ok; 610 false -> ct_gen_conn:start_log(Heading) 611 end. 612 613%%%----------------------------------------------------------------- 614end_gen_log() -> 615 %% check if output is suppressed 616 case ct_util:is_silenced(telnet) of 617 true -> ok; 618 false -> ct_gen_conn:end_log() 619 end. 620 621%% Debug printouts. 622debug_cont_gen_log(Str,Args) -> 623 Old = put(silent,true), 624 ct_gen_conn:cont_log(Str,Args), 625 put(silent,Old). 626 627%% Log callback - called from the error handler process 628format_data(_How,{String,Args}) -> 629 io_lib:format(String,Args). 630 631%%%================================================================= 632%%% Abstraction layer on top of ct_telnet_client.erl 633teln_cmd(Pid,Cmd,Prx,Newline,Timeout) -> 634 ct_telnet_client:send_data(Pid,Cmd,Newline), 635 teln_receive_until_prompt(Pid,Prx,Timeout). 636 637teln_get_all_data(State=#state{teln_pid=Pid,prx=Prx},Data,Acc,LastLine,Polls) -> 638 case check_for_prompt(Prx,LastLine++Data) of 639 {prompt,Lines,_PromptType,Rest} -> 640 teln_get_all_data(State,Rest,[Lines|Acc],[],State#state.poll_limit); 641 {noprompt,Lines,LastLine1} -> 642 case ct_telnet_client:get_data(Pid) of 643 {ok,[]} when LastLine1 /= [], Polls > 0 -> 644 %% No more data from server but the last string is not 645 %% a complete line (maybe because of a slow connection), 646 timer:sleep(State#state.poll_interval), 647 NewPolls = if Polls == infinity -> infinity; 648 true -> Polls-1 649 end, 650 teln_get_all_data(State,[],[Lines|Acc],LastLine1,NewPolls); 651 {ok,[]} -> 652 {ok,lists:reverse(lists:append([Lines|Acc])),LastLine1}; 653 {ok,Data1} -> 654 teln_get_all_data(State,Data1,[Lines|Acc],LastLine1, 655 State#state.poll_limit) 656 end 657 end. 658 659%% Expect options record 660-record(eo,{teln_pid, 661 prx, 662 idle_timeout, 663 total_timeout, 664 haltpatterns=[], 665 seq=false, 666 repeat=false, 667 found_prompt=false, 668 prompt_check=true}). 669 670%% Externally the silent_teln_expect function shall only be used 671%% by the TargetModule, i.e. the target specific module which 672%% implements connect/2 and get_prompt_regexp/0. 673silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) -> 674 Old = put(silent,true), 675 Result = teln_expect(Name,Pid,Data,Pattern,Prx,Opts), 676 put(silent,Old), 677 Result. 678 679%% teln_expect/6 680%% 681%% This function implements the expect functionality over telnet. In 682%% general there are three possible ways to go: 683%% 1) Single: One or more patterns are given, and the function return 684%% when one of the patterns are matched. 685%% 2) Sequence: Several patterns are given, and they are matched in 686%% the order they appear in the pattern list. 687%% 3a) Repeat (single): 1) is repeated either N times or until a halt 688%% condition is fulfilled. 689%% 3b) Repeat (sequence): 2) is repeated either N times or until a 690%% halt condition is fulfilled. 691teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> 692 HaltPatterns0 = 693 case get_ignore_prompt(Opts) of 694 true -> 695 get_haltpatterns(Opts); 696 false -> 697 [prompt | get_haltpatterns(Opts)] 698 end, 699 case convert_pattern(HaltPatterns0,false) of 700 {ok,HaltPatterns} -> 701 {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts), 702 Seq = get_seq(Opts1), 703 case convert_pattern(Pattern1,Seq) of 704 {ok,Pattern2} -> 705 {IdleTimeout,TotalTimeout} = get_timeouts(Opts1), 706 PromptCheck = get_prompt_check(Opts1), 707 708 EO = #eo{teln_pid=Pid, 709 prx=Prx, 710 idle_timeout=IdleTimeout, 711 total_timeout=TotalTimeout, 712 seq=Seq, 713 haltpatterns=HaltPatterns, 714 prompt_check=PromptCheck}, 715 716 case get_repeat(Opts1) of 717 false -> 718 case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of 719 {ok,Matched,Rest} when WaitForPrompt -> 720 case lists:reverse(Matched) of 721 [{prompt,_},Matched1] -> 722 {ok,Matched1,Rest}; 723 [{prompt,_}|Matched1] -> 724 {ok,lists:reverse(Matched1),Rest} 725 end; 726 {ok,Matched,Rest} -> 727 {ok,Matched,Rest}; 728 {halt,Why,Rest} -> 729 {error,Why,Rest}; 730 {error,Reason} -> 731 {error,Reason} 732 end; 733 N -> 734 EO1 = EO#eo{repeat=N}, 735 repeat_expect(Name,Pid,Data,Pattern2,[],EO1) 736 end; 737 Error -> 738 Error 739 end; 740 Error -> 741 Error 742 end. 743 744convert_pattern(Pattern0,Seq) 745 when Pattern0==[] orelse (is_list(Pattern0) and not is_integer(hd(Pattern0))) -> 746 Pattern = 747 case Seq of 748 true -> Pattern0; 749 false -> rm_dupl(Pattern0,[]) 750 end, 751 compile_pattern(Pattern,[]); 752convert_pattern(Pattern,_Seq) -> 753 compile_pattern([Pattern],[]). 754 755rm_dupl([P|Ps],Acc) -> 756 case lists:member(P,Acc) of 757 true -> 758 rm_dupl(Ps,Acc); 759 false -> 760 rm_dupl(Ps,[P|Acc]) 761 end; 762rm_dupl([],Acc) -> 763 lists:reverse(Acc). 764 765compile_pattern([prompt|Patterns],Acc) -> 766 compile_pattern(Patterns,[prompt|Acc]); 767compile_pattern([{prompt,_}=P|Patterns],Acc) -> 768 compile_pattern(Patterns,[P|Acc]); 769compile_pattern([{Tag,Pattern}|Patterns],Acc) -> 770 try re:compile(Pattern,[unicode]) of 771 {ok,MP} -> compile_pattern(Patterns,[{Tag,MP}|Acc]); 772 {error,Error} -> {error,{bad_pattern,{Tag,Pattern},Error}} 773 catch error:badarg -> {error,{bad_pattern,{Tag,Pattern}}} 774 end; 775compile_pattern([Pattern|Patterns],Acc) -> 776 try re:compile(Pattern,[unicode]) of 777 {ok,MP} -> compile_pattern(Patterns,[MP|Acc]); 778 {error,Error} -> {error,{bad_pattern,Pattern,Error}} 779 catch error:badarg -> {error,{bad_pattern,Pattern}} 780 end; 781compile_pattern([],Acc) -> 782 {ok,lists:reverse(Acc)}. 783 784get_timeouts(Opts) -> 785 {case lists:keysearch(idle_timeout,1,Opts) of 786 {value,{_,T}} -> 787 T; 788 false -> 789 %% this check is for backwards compatibility (pre CT v1.8) 790 case lists:keysearch(timeout,1,Opts) of 791 {value,{_,T}} -> T; 792 false -> ?DEFAULT_TIMEOUT 793 end 794 end, 795 case lists:keysearch(total_timeout,1,Opts) of 796 {value,{_,T}} -> T; 797 false -> infinity 798 end}. 799 800get_repeat(Opts) -> 801 case lists:keysearch(repeat,1,Opts) of 802 {value,{repeat,N}} when is_integer(N) -> 803 N; 804 false -> 805 case lists:member(repeat,Opts) of 806 true -> 807 -1; 808 false -> 809 false 810 end 811 end. 812get_seq(Opts) -> 813 lists:member(sequence,Opts). 814get_haltpatterns(Opts) -> 815 case lists:keysearch(halt,1,Opts) of 816 {value,{halt,HaltPatterns}} -> 817 HaltPatterns; 818 false -> 819 [] 820 end. 821get_ignore_prompt(Opts) -> 822 lists:member(ignore_prompt,Opts). 823get_prompt_check(Opts) -> 824 not lists:member(no_prompt_check,Opts). 825 826wait_for_prompt(Pattern, Opts) -> 827 case lists:member(wait_for_prompt, Opts) of 828 true -> 829 wait_for_prompt1(prompt, Pattern, 830 lists:delete(wait_for_prompt,Opts)); 831 false -> 832 case proplists:get_value(wait_for_prompt, Opts) of 833 undefined -> 834 {false,Pattern,Opts}; 835 PromptStr -> 836 wait_for_prompt1({prompt,PromptStr}, Pattern, 837 proplists:delete(wait_for_prompt,Opts)) 838 end 839 end. 840 841wait_for_prompt1(Prompt, [Ch|_] = Pattern, Opts) when is_integer(Ch) -> 842 wait_for_prompt2(Prompt, [Pattern], Opts); 843wait_for_prompt1(Prompt, Pattern, Opts) when is_list(Pattern) -> 844 wait_for_prompt2(Prompt, Pattern, Opts); 845wait_for_prompt1(Prompt, Pattern, Opts) -> 846 wait_for_prompt2(Prompt, [Pattern], Opts). 847 848wait_for_prompt2(Prompt, Pattern, Opts) -> 849 Pattern1 = case lists:reverse(Pattern) of 850 [prompt|_] -> Pattern; 851 [{prompt,_}|_] -> Pattern; 852 _ -> Pattern ++ [Prompt] 853 end, 854 Opts1 = case lists:member(sequence, Opts) of 855 true -> Opts; 856 false -> [sequence|Opts] 857 end, 858 {true,Pattern1,Opts1}. 859 860%% Repeat either single or sequence. All match results are accumulated 861%% and returned when a halt condition is fulfilled. 862repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) -> 863 {ok,lists:reverse(Acc),done,Rest}; 864repeat_expect(Name,Pid,Data,Pattern,Acc,EO) -> 865 case teln_expect1(Name,Pid,Data,Pattern,[],EO) of 866 {ok,Matched,Rest} -> 867 EO1 = EO#eo{repeat=EO#eo.repeat-1}, 868 repeat_expect(Name,Pid,Rest,Pattern,[Matched|Acc],EO1); 869 {halt,Why,Rest} -> 870 {ok,lists:reverse(Acc),Why,Rest}; 871 {error,Reason} -> 872 {error,Reason} 873 end. 874 875teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO, 876 total_timeout=TotalTO}) -> 877 %% TotalTO is a float value in this loop (unless it's 'infinity'), 878 %% but an integer value will be passed to the other functions 879 EOMod = if TotalTO /= infinity -> EO#eo{total_timeout=trunc(TotalTO)}; 880 true -> EO 881 end, 882 ExpectFun = case EOMod#eo.seq of 883 true -> fun() -> 884 seq_expect(Name,Pid,Data,Pattern,Acc,EOMod) 885 end; 886 false -> fun() -> 887 one_expect(Name,Pid,Data,Pattern,EOMod) 888 end 889 end, 890 case ExpectFun() of 891 {match,Match,Rest} -> 892 {ok,Match,Rest}; 893 {halt,Why,Rest} -> 894 {halt,Why,Rest}; 895 NotFinished -> 896 %% Get more data 897 Fun = fun() -> get_data1(EOMod#eo.teln_pid) end, 898 BreakAfter = if TotalTO < IdleTO -> 899 %% use the integer value 900 EOMod#eo.total_timeout; 901 true -> 902 IdleTO 903 end, 904 {PatOrPats1,Acc1,Rest1} = case NotFinished of 905 {nomatch,Rest0} -> 906 %% one expect 907 {Pattern,[],Rest0}; 908 {continue,Pats0,Acc0,Rest0} -> 909 %% sequence 910 {Pats0,Acc0,Rest0} 911 end, 912 case timer:tc(ct_gen_conn, do_within_time, [Fun,BreakAfter]) of 913 {_,{error,Reason}} -> 914 %% A timeout will occur when the telnet connection 915 %% is idle for EO#eo.idle_timeout milliseconds. 916 if Rest1 /= [] -> 917 log(name_or_pid(Name,Pid)," ~ts",[Rest1]); 918 true -> 919 ok 920 end, 921 {error,Reason}; 922 {_,{ok,Data1}} when TotalTO == infinity -> 923 teln_expect1(Name,Pid,Rest1++Data1,PatOrPats1,Acc1,EOMod); 924 {Elapsed,{ok,Data1}} -> 925 TVal = TotalTO - (Elapsed/1000), 926 if TVal =< 0 -> 927 {error,timeout}; 928 true -> 929 EO1 = EO#eo{total_timeout = TVal}, 930 teln_expect1(Name,Pid,Rest1++Data1, 931 PatOrPats1,Acc1,EO1) 932 end 933 end 934 end. 935 936get_data1(Pid) -> 937 case ct_telnet_client:get_data(Pid) of 938 {ok,[]} -> 939 get_data1(Pid); 940 {ok,Data} -> 941 {ok,Data} 942 end. 943 944%% 1) Single expect. 945%% First the whole data chunk is searched for a prompt (to avoid doing 946%% a regexp match for the prompt at each line). 947%% If we are searching for anything else, the datachunk is split into 948%% lines and each line is matched against each pattern. 949 950%% one_expect: split data chunk at prompts 951one_expect(Name,Pid,Data,Pattern,EO) when EO#eo.prompt_check==false -> 952% io:format("Raw Data ~tp Pattern ~tp EO ~tp ",[Data,Pattern,EO]), 953 one_expect1(Name,Pid,Data,Pattern,[],EO#eo{found_prompt=false}); 954one_expect(Name,Pid,Data,Pattern,EO) -> 955 case match_prompt(Data,EO#eo.prx) of 956 {prompt,UptoPrompt,PromptType,Rest} -> 957 case Pattern of 958 [Prompt] when Prompt==prompt; Prompt=={prompt,PromptType} -> 959 %% Only searching for prompt 960 log_lines(Name,Pid,UptoPrompt), 961 log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]), 962 {match,{prompt,PromptType},Rest}; 963 [{prompt,_OtherPromptType}] -> 964 %% Only searching for one specific prompt, not this one 965 log_lines(Name,Pid,UptoPrompt), 966 {nomatch,Rest}; 967 _ -> 968 one_expect1(Name,Pid,UptoPrompt,Pattern,Rest, 969 EO#eo{found_prompt=PromptType}) 970 end; 971 noprompt -> 972 case Pattern of 973 [Prompt] when Prompt==prompt; element(1,Prompt)==prompt -> 974 %% Only searching for prompt 975 LastLine = log_lines_not_last(Name,Pid,Data), 976 {nomatch,LastLine}; 977 _ -> 978 one_expect1(Name,Pid,Data,Pattern,[], 979 EO#eo{found_prompt=false}) 980 end 981 end. 982 983%% one_expect1: split data chunk at lines 984one_expect1(Name,Pid,Data,Pattern,Rest,EO) -> 985 case match_lines(Name,Pid,Data,Pattern,EO) of 986 {match,Match,MatchRest} -> 987 {match,Match,MatchRest++Rest}; 988 {nomatch,prompt} -> 989 one_expect(Name,Pid,Rest,Pattern,EO); 990 {nomatch,NoMatchRest} -> 991 {nomatch,NoMatchRest++Rest}; 992 {halt,Why,HaltRest} -> 993 {halt,Why,HaltRest++Rest} 994 end. 995 996 997%% 2) Sequence. 998%% First the whole data chunk is searched for a prompt (to avoid doing 999%% a regexp match for the prompt at each line). 1000%% If we are searching for anything else, the datachunk is split into 1001%% lines and each line is matched against the first pattern in the list. 1002%% When a match is found, the match result is accumulated, and we keep 1003%% searching for the next pattern in the list. 1004 1005%% seq_expect: Split data chunk at prompts 1006seq_expect(_Name,_Pid,Data,[],Acc,_EO) -> 1007 {match,lists:reverse(Acc),Data}; 1008seq_expect(_Name,_Pid,[],Patterns,Acc,_EO) -> 1009 {continue,Patterns,lists:reverse(Acc),[]}; 1010seq_expect(Name,Pid,Data,Patterns,Acc,EO) when EO#eo.prompt_check==false -> 1011 seq_expect1(Name,Pid,Data,Patterns,Acc,[],EO#eo{found_prompt=false}); 1012seq_expect(Name,Pid,Data,Patterns,Acc,EO) -> 1013 case match_prompt(Data,EO#eo.prx) of 1014 {prompt,UptoPrompt,PromptType,Rest} -> 1015 seq_expect1(Name,Pid,UptoPrompt,Patterns,Acc,Rest, 1016 EO#eo{found_prompt=PromptType}); 1017 noprompt -> 1018 seq_expect1(Name,Pid,Data,Patterns,Acc,[],EO#eo{found_prompt=false}) 1019 end. 1020 1021%% seq_expect1: For one prompt-chunk, match each pattern - line by 1022%% line if it is other than the prompt we are seaching for. 1023seq_expect1(Name,Pid,Data,[prompt|Patterns],Acc,Rest,EO) -> 1024 case EO#eo.found_prompt of 1025 false -> 1026 LastLine = log_lines_not_last(Name,Pid,Data), 1027 %% Rest==[] because no prompt is found 1028 {continue,[prompt|Patterns],Acc,LastLine}; 1029 PromptType -> 1030 log_lines(Name,Pid,Data), 1031 log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]), 1032 seq_expect(Name,Pid,Rest,Patterns,[{prompt,PromptType}|Acc],EO) 1033 end; 1034seq_expect1(Name,Pid,Data,[{prompt,PromptType}|Patterns],Acc,Rest,EO) -> 1035 case EO#eo.found_prompt of 1036 false -> 1037 LastLine = log_lines_not_last(Name,Pid,Data), 1038 %% Rest==[] because no prompt is found 1039 {continue,[{prompt,PromptType}|Patterns],Acc,LastLine}; 1040 PromptType -> 1041 log_lines(Name,Pid,Data), 1042 log(name_or_pid(Name,Pid),"PROMPT: ~ts", [PromptType]), 1043 seq_expect(Name,Pid,Rest,Patterns,[{prompt,PromptType}|Acc],EO); 1044 _OtherPromptType -> 1045 log_lines(Name,Pid,Data), 1046 seq_expect(Name,Pid,Rest,[{prompt,PromptType}|Patterns],Acc,EO) 1047 end; 1048seq_expect1(Name,Pid,Data,[Pattern|Patterns],Acc,Rest,EO) -> 1049 case match_lines(Name,Pid,Data,[Pattern],EO) of 1050 {match,Match,MatchRest} -> 1051 seq_expect1(Name,Pid,MatchRest,Patterns,[Match|Acc],Rest,EO); 1052 {nomatch,prompt} -> 1053 seq_expect(Name,Pid,Rest,[Pattern|Patterns],Acc,EO); 1054 {nomatch,NoMatchRest} when Rest==[] -> 1055 %% The data did not end with a prompt 1056 {continue,[Pattern|Patterns],Acc,NoMatchRest}; 1057 {halt,Why,HaltRest} -> 1058 {halt,Why,HaltRest++Rest} 1059 end; 1060seq_expect1(_Name,_Pid,Data,[],Acc,Rest,_EO) -> 1061 {match,lists:reverse(Acc),Data++Rest}. 1062 1063%% Split prompt-chunk at lines 1064match_lines(Name,Pid,Data,Patterns,EO) -> 1065 FoundPrompt = EO#eo.found_prompt, 1066 case one_line(Data,[]) of 1067 {noline,Rest} when FoundPrompt=/=false -> 1068 %% This is the line including the prompt 1069 case match_line(Name,Pid,Rest,Patterns,FoundPrompt,false,EO) of 1070 nomatch -> 1071 {nomatch,prompt}; 1072 {Tag,Match} -> 1073 {Tag,Match,[]} 1074 end; 1075 {noline,Rest} when EO#eo.prompt_check==false -> 1076 case match_line(Name,Pid,Rest,Patterns,false,false,EO) of 1077 nomatch -> 1078 {nomatch,Rest}; 1079 {Tag,Match} -> 1080 {Tag,Match,[]} 1081 end; 1082 {noline,Rest} -> 1083 {nomatch,Rest}; 1084 {Line,Rest} -> 1085 case match_line(Name,Pid,Line,Patterns,false,true,EO) of 1086 nomatch -> 1087 match_lines(Name,Pid,Rest,Patterns,EO); 1088 {Tag,Match} -> 1089 {Tag,Match,Rest} 1090 end 1091 end. 1092 1093%% For one line, match each pattern 1094match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO) -> 1095 match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO,match). 1096 1097match_line(Name,Pid,Line,[prompt|Patterns],false,Term,EO,RetTag) -> 1098 match_line(Name,Pid,Line,Patterns,false,Term,EO,RetTag); 1099match_line(Name,Pid,Line,[prompt|_Patterns],FoundPrompt,_Term,_EO,RetTag) -> 1100 log(name_or_pid(Name,Pid)," ~ts",[Line]), 1101 log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), 1102 {RetTag,{prompt,FoundPrompt}}; 1103match_line(Name,Pid,Line,[{prompt,PromptType}|_Patterns],FoundPrompt,_Term, 1104 _EO,RetTag) when PromptType==FoundPrompt -> 1105 log(name_or_pid(Name,Pid)," ~ts",[Line]), 1106 log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), 1107 {RetTag,{prompt,FoundPrompt}}; 1108match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,Term, 1109 EO,RetTag) 1110 when PromptType=/=FoundPrompt -> 1111 match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); 1112match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,Term,EO,RetTag) -> 1113 case re:run(Line,Pattern,[{capture,all,list}]) of 1114 nomatch -> 1115 match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); 1116 {match,Match} -> 1117 log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), 1118 {RetTag,{Tag,Match}} 1119 end; 1120match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) -> 1121 case re:run(Line,Pattern,[{capture,all,list}]) of 1122 nomatch -> 1123 match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); 1124 {match,Match} -> 1125 log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), 1126 {RetTag,Match} 1127 end; 1128match_line(Name,Pid,Line,[],FoundPrompt,Term,EO,match) -> 1129 match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,Term,EO,halt); 1130%% print any terminated line that cannot be matched 1131match_line(Name,Pid,Line,[],_FoundPrompt,true,_EO,halt) -> 1132 log(name_or_pid(Name,Pid)," ~ts",[Line]), 1133 nomatch; 1134%% if there's no line termination, Line is saved as Rest (above) and will 1135%% be printed later 1136match_line(_Name,_Pid,_Line,[],_FoundPrompt,false,_EO,halt) -> 1137 nomatch. 1138 1139one_line([$\n|Rest],Line) -> 1140 {lists:reverse(Line),Rest}; 1141one_line([$\r|Rest],Line) -> 1142 one_line(Rest,Line); 1143one_line([0|Rest],Line) -> 1144 one_line(Rest,Line); 1145one_line([Char|Rest],Line) -> 1146 one_line(Rest,[Char|Line]); 1147one_line([],Line) -> 1148 {noline,lists:reverse(Line)}. 1149 1150debug_log_lines(String) -> 1151 Old = put(silent,true), 1152 log_lines(undefined,undefined,String), 1153 put(silent,Old). 1154 1155log_lines(Name,Pid,String) -> 1156 case log_lines_not_last(Name,Pid,String) of 1157 [] -> 1158 ok; 1159 LastLine -> 1160 log(name_or_pid(Name,Pid)," ~ts",[LastLine]) 1161 end. 1162 1163log_lines_not_last(Name,Pid,String) -> 1164 case add_tabs(String,[],[]) of 1165 {[],LastLine} -> 1166 LastLine; 1167 {String1,LastLine} -> 1168 log(name_or_pid(Name,Pid),"~ts",[String1]), 1169 LastLine 1170 end. 1171 1172name_or_pid(undefined,Pid) -> Pid; 1173name_or_pid(Name,_) -> Name. 1174 1175add_tabs([0|Rest],Acc,LastLine) -> 1176 add_tabs(Rest,Acc,LastLine); 1177add_tabs([$\r|Rest],Acc,LastLine) -> 1178 add_tabs(Rest,Acc,LastLine); 1179add_tabs([$\n|Rest],Acc,LastLine) -> 1180 add_tabs(Rest,[$\n|LastLine] ++ [$\s,$\s,$\s,$\s,$\s,$\s,$\s|Acc],[]); 1181add_tabs([Ch|Rest],Acc,LastLine) -> 1182 add_tabs(Rest,Acc,[Ch|LastLine]); 1183add_tabs([],[$\n|Acc],LastLine) -> 1184 {lists:reverse(Acc),lists:reverse(LastLine)}; 1185add_tabs([],[],LastLine) -> 1186 {[],lists:reverse(LastLine)}. 1187 1188teln_receive_until_prompt(Pid,Prx,Timeout) -> 1189 Fun = fun() -> teln_receive_until_prompt(Pid,Prx,[],[]) end, 1190 ct_gen_conn:do_within_time(Fun, Timeout). 1191 1192teln_receive_until_prompt(Pid,Prx,Acc,LastLine) -> 1193 {ok,Data} = ct_telnet_client:get_data(Pid), 1194 case check_for_prompt(Prx,LastLine++Data) of 1195 {prompt,Lines,PromptType,Rest} -> 1196 Return = lists:reverse(lists:append([Lines|Acc])), 1197 {ok,Return,PromptType,Rest}; 1198 {noprompt,Lines,LastLine1} -> 1199 teln_receive_until_prompt(Pid,Prx,[Lines|Acc],LastLine1) 1200 end. 1201 1202check_for_prompt(Prx,Data) -> 1203 case match_prompt(Data,Prx) of 1204 {prompt,UptoPrompt,PromptType,Rest} -> 1205 {RevLines,LastLine} = split_lines(UptoPrompt), 1206 {prompt,[LastLine|RevLines],PromptType,Rest}; 1207 noprompt -> 1208 {RevLines,Rest} = split_lines(Data), 1209 {noprompt,RevLines,Rest} 1210 end. 1211 1212split_lines(String) -> 1213 split_lines(String,[],[]). 1214split_lines([$\n|Rest],Line,Lines) when Line /= [] -> 1215 split_lines(Rest,[],[lists:reverse(Line)|Lines]); 1216split_lines([$\n|Rest],[],Lines) -> 1217 split_lines(Rest,[],Lines); 1218split_lines([$\r|Rest],Line,Lines) -> 1219 split_lines(Rest,Line,Lines); 1220split_lines([0|Rest],Line,Lines) -> 1221 split_lines(Rest,Line,Lines); 1222split_lines([Char|Rest],Line,Lines) -> 1223 split_lines(Rest,[Char|Line],Lines); 1224split_lines([],Line,Lines) -> 1225 {Lines,lists:reverse(Line)}. 1226 1227 1228match_prompt(Str,Prx) -> 1229 match_prompt(Str,Prx,[]). 1230match_prompt(Str,Prx,Acc) -> 1231 case re:run(Str,Prx,[unicode]) of 1232 nomatch -> 1233 noprompt; 1234 {match,[{Start,Len}]} -> 1235 case split_prompt_string(Str,Start+1,Start+Len,1,[],[]) of 1236 {noprompt,Done,Rest} -> 1237 match_prompt(Rest,Prx,Done); 1238 {prompt,UptoPrompt,Prompt,Rest} -> 1239 {prompt,lists:reverse(UptoPrompt++Acc), 1240 lists:reverse(Prompt),Rest} 1241 end 1242 end. 1243 1244split_prompt_string([Ch|Str],Start,End,N,UptoPrompt,Prompt) when N<Start -> 1245 split_prompt_string(Str,Start,End,N+1,[Ch|UptoPrompt],Prompt); 1246split_prompt_string([Ch|Str],Start,End,N,UptoPrompt,Prompt) 1247 when N>=Start, N<End-> 1248 split_prompt_string(Str,Start,End,N+1,UptoPrompt,[Ch|Prompt]); 1249split_prompt_string([Ch|Rest],_Start,End,N,UptoPrompt,Prompt) when N==End -> 1250 case UptoPrompt of 1251 [$",$=,$T,$P,$M,$O,$R,$P|_] -> 1252 %% This is a line from "listenv", it is not a real prompt 1253 {noprompt,[Ch|Prompt]++UptoPrompt,Rest}; 1254 [$\s,$t,$s,$a|_] when Prompt==":nigol" -> 1255 %% This is probably the "Last login:" statement which is 1256 %% written when telnet connection is openend. 1257 {noprompt,[Ch|Prompt]++UptoPrompt,Rest}; 1258 _ -> 1259 {prompt,[Ch|Prompt]++UptoPrompt,[Ch|Prompt],Rest} 1260 end. 1261