diff options
Diffstat (limited to 'lib')
137 files changed, 3646 insertions, 1829 deletions
diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl index dd7e03dd28..410f598665 100644 --- a/lib/compiler/src/beam_a.erl +++ b/lib/compiler/src/beam_a.erl @@ -91,6 +91,10 @@ rename_instr({bs_private_append=I,F,Sz,U,Src,Flags,Dst}) -> {bs_init,F,{I,U,Flags},none,[Sz,Src],Dst}; rename_instr(bs_init_writable=I) -> {bs_init,{f,0},I,1,[{x,0}],{x,0}}; +rename_instr({test,Op,F,[Ctx,Bits,{string,Str}]}) -> + %% When compiling from a .S file. + <<Bs:Bits/bits,_/bits>> = list_to_binary(Str), + {test,Op,F,[Ctx,Bs]}; rename_instr({put_map_assoc,Fail,S,D,R,L}) -> {put_map,Fail,assoc,S,D,R,L}; rename_instr({put_map_exact,Fail,S,D,R,L}) -> diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index f8cf178d2e..084686def7 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -132,10 +132,10 @@ build_file(Code, Attr, Dict, NumLabels, NumFuncs, Abst, SourceFile, Opts) -> LiteralChunk = case beam_dict:literal_table(Dict) of {0,[]} -> []; {NumLiterals,LitTab0} -> - LitTab1 = iolist_to_binary(LitTab0), - LitTab2 = <<NumLiterals:32,LitTab1/binary>>, - LitTab = iolist_to_binary(zlib:compress(LitTab2)), - chunk(<<"LitT">>, <<(byte_size(LitTab2)):32>>, LitTab) + LitTab1 = [<<NumLiterals:32>>,LitTab0], + LitTab = zlib:compress(LitTab1), + chunk(<<"LitT">>, <<(iolist_size(LitTab1)):32>>, + LitTab) end, %% Create the line chunk. diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl index 5216f39296..e2639e9cac 100644 --- a/lib/compiler/src/beam_block.erl +++ b/lib/compiler/src/beam_block.erl @@ -61,15 +61,6 @@ blockify(Is) -> blockify([{loop_rec,{f,Fail},{x,0}},{loop_rec_end,_Lbl},{label,Fail}|Is], Acc) -> %% Useless instruction sequence. blockify(Is, Acc); - -%% New bit syntax matching. -blockify([{bs_save2,R,Point}=I,{bs_restore2,R,Point}|Is], Acc) -> - blockify([I|Is], Acc); -blockify([{bs_save2,R,Point}=I,{test,is_eq_exact,_,_}=Test, - {bs_restore2,R,Point}|Is], Acc) -> - blockify([I,Test|Is], Acc); - -%% Do other peep-hole optimizations. blockify([{test,is_atom,{f,Fail},[Reg]}=I| [{select,select_val,Reg,{f,Fail}, [{atom,false},{f,_}=BrFalse, diff --git a/lib/compiler/src/beam_bsm.erl b/lib/compiler/src/beam_bsm.erl index d54c2a9fde..427b7071ac 100644 --- a/lib/compiler/src/beam_bsm.erl +++ b/lib/compiler/src/beam_bsm.erl @@ -20,7 +20,7 @@ -module(beam_bsm). -export([module/2,format_error/1]). --import(lists, [member/2,foldl/3,reverse/1,sort/1,all/2,dropwhile/2]). +-import(lists, [member/2,foldl/3,reverse/1,sort/1,all/2]). %%% %%% We optimize bit syntax matching where the tail end of a binary is @@ -542,16 +542,13 @@ btb_context_regs_1(Regs, N, Tag, Acc) -> %% a binary. MustSave is true if the function may pass the match %% context to the bs_context_to_binary instruction (in which case %% the current position in the binary must have saved into the -%% start position using "bs_save_2 Ctx start". +%% start position using "bs_save_2 Ctx start"). btb_index(Fs) -> btb_index_1(Fs, []). btb_index_1([{function,_,_,Entry,Is0}|Fs], Acc0) -> - [{label,Entry}|Is] = - dropwhile(fun({label,L}) when L =:= Entry -> false; - (_) -> true - end, Is0), + Is = drop_to_label(Is0, Entry), Acc = btb_index_2(Is, Entry, false, Acc0), btb_index_1(Fs, Acc); btb_index_1([], Acc) -> gb_trees:from_orddict(sort(Acc)). @@ -566,6 +563,9 @@ btb_index_2(Is0, Entry, _, Acc) -> throw:none -> Acc end. +drop_to_label([{label,L}|Is], L) -> Is; +drop_to_label([_|Is], L) -> drop_to_label(Is, L). + btb_index_find_start_match([{test,_,{f,F},_},{bs_context_to_binary,_}|Is]) -> btb_index_find_label(Is, F); btb_index_find_start_match(_) -> diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl index b68b8702e0..1d26993103 100644 --- a/lib/compiler/src/beam_clean.erl +++ b/lib/compiler/src/beam_clean.erl @@ -184,14 +184,6 @@ function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) -> function_replace(Fs, Dict, [{function,Name,Arity,Entry,Asm}|Acc]); function_replace([], _, Acc) -> Acc. -replace([{test,bs_match_string=Op,{f,Lbl},[Ctx,Bin0]}|Is], Acc, D) -> - Bits = bit_size(Bin0), - Bin = case Bits rem 8 of - 0 -> Bin0; - Rem -> <<Bin0/bitstring,0:(8-Rem)>> - end, - I = {test,Op,{f,label(Lbl, D)},[Ctx,Bits,{string,binary_to_list(Bin)}]}, - replace(Is, [I|Acc], D); replace([{test,Test,{f,Lbl},Ops}|Is], Acc, D) -> replace(Is, [{test,Test,{f,label(Lbl, D)},Ops}|Acc], D); replace([{test,Test,{f,Lbl},Live,Ops,Dst}|Is], Acc, D) -> diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl index f4515ba2a7..5932d8ce1d 100644 --- a/lib/compiler/src/beam_dead.erl +++ b/lib/compiler/src/beam_dead.erl @@ -255,6 +255,16 @@ backward([{jump,{f,To}}=J|[{bif,Op,_,Ops,Reg}|Is]=Is0], D, Acc) -> catch throw:not_possible -> backward(Is0, D, [J|Acc]) end; +backward([{test,bs_start_match2,F,_,[R,_],Ctxt}=I|Is], D, + [{test,bs_match_string,F,[Ctxt,Bs]}, + {test,bs_test_tail2,F,[Ctxt,0]}|Acc0]=Acc) -> + case beam_utils:is_killed(Ctxt, Acc0, D) of + true -> + Eq = {test,is_eq_exact,F,[R,{literal,Bs}]}, + backward(Is, D, [Eq|Acc0]); + false -> + backward(Is, D, [I|Acc]) + end; backward([{test,bs_start_match2,{f,To0},Live,[Src|_]=Info,Dst}|Is], D, Acc) -> To = shortcut_bs_start_match(To0, Src, D), I = {test,bs_start_match2,{f,To},Live,Info,Dst}, @@ -459,8 +469,8 @@ count_bits_matched([{test,_,_,_,[_,Sz,U,{field_flags,_}],_}|Is], SavePoint, Bits {integer,N} -> count_bits_matched(Is, SavePoint, Bits+N*U); _ -> count_bits_matched(Is, SavePoint, Bits) end; -count_bits_matched([{test,bs_match_string,_,[_,Bits,_]}|Is], SavePoint, Bits0) -> - count_bits_matched(Is, SavePoint, Bits0+Bits); +count_bits_matched([{test,bs_match_string,_,[_,Bs]}|Is], SavePoint, Bits) -> + count_bits_matched(Is, SavePoint, Bits+bit_size(Bs)); count_bits_matched([{test,_,_,_}|Is], SavePoint, Bits) -> count_bits_matched(Is, SavePoint, Bits); count_bits_matched([{bs_save2,Reg,SavePoint}|_], {Reg,SavePoint}, Bits) -> diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index ba71d4efae..52b6464c7f 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -127,7 +127,7 @@ %%% on the program state. %%% --import(lists, [reverse/1,reverse/2,foldl/3,dropwhile/2]). +-import(lists, [reverse/1,reverse/2,foldl/3]). module({Mod,Exp,Attr,Fs0,Lc}, _Opt) -> Fs = [function(F) || F <- Fs0], @@ -509,10 +509,7 @@ rem_unused([{label,Lbl}=I|Is0], Used, [Prev|_]=Acc) -> case gb_sets:is_member(Lbl, Used) of false -> Is = case is_unreachable_after(Prev) of - true -> - dropwhile(fun({label,_}) -> false; - (_) -> true - end, Is0); + true -> drop_upto_label(Is0); false -> Is0 end, rem_unused(Is, Used, Acc); @@ -533,6 +530,10 @@ initial_labels([{label,Lbl}|Is], Acc) -> initial_labels([{func_info,_,_,_},{label,Lbl}|_], Acc) -> gb_sets:from_list([Lbl|Acc]). +drop_upto_label([{label,_}|_]=Is) -> Is; +drop_upto_label([_|Is]) -> drop_upto_label(Is); +drop_upto_label([]) -> []. + %% ulbl(Instruction, UsedGbSet) -> UsedGbSet' %% Update the gb_set UsedGbSet with any function-local labels %% (i.e. not with labels in call instructions) referenced by diff --git a/lib/compiler/src/beam_type.erl b/lib/compiler/src/beam_type.erl index 26c933481a..4731b5e78e 100644 --- a/lib/compiler/src/beam_type.erl +++ b/lib/compiler/src/beam_type.erl @@ -149,9 +149,10 @@ simplify_basic_1([], Ts, Acc) -> %% simplify_float(Is0, Ts0) -> {Is1,Ts} = simplify_float_1(Is0, Ts0, [], []), - Is2 = flt_need_heap(Is1), + Is2 = opt_fmoves(Is1, []), + Is3 = flt_need_heap(Is2), try - {flt_liveness(Is2),Ts} + {flt_liveness(Is3),Ts} catch throw:not_possible -> not_possible end. @@ -202,14 +203,15 @@ simplify_float_1([{set,_,_,{'catch',_}}=I|Is]=Is0, _Ts, Rs0, Acc0) -> simplify_float_1(Is, tdb_new(), Rs0, [I|Acc]); simplify_float_1([{set,_,_,{line,_}}=I|Is], Ts, Rs, Acc) -> simplify_float_1(Is, Ts, Rs, [I|Acc]); +simplify_float_1([I|Is], Ts0, [], Acc) -> + Ts = update(I, Ts0), + simplify_float_1(Is, Ts, [], [I|Acc]); simplify_float_1([I|Is]=Is0, Ts0, Rs0, Acc0) -> Ts = update(I, Ts0), {Rs,Acc} = flush(Rs0, Is0, Acc0), simplify_float_1(Is, Ts, Rs, [I|checkerror(Acc)]); -simplify_float_1([], Ts, Rs, Acc0) -> - Acc = checkerror(Acc0), - Is0 = reverse(flush_all(Rs, [], Acc)), - Is = opt_fmoves(Is0, []), +simplify_float_1([], Ts, [], Acc) -> + Is = reverse(Acc), {Is,Ts}. coerce_to_float({integer,I}=Int) -> diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index 7704690f86..fd666be41e 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -612,13 +612,15 @@ is_reg_used_at_1(R, Lbl, St0) -> end. index_labels_1([{label,Lbl}|Is0], Acc) -> - Is = lists:dropwhile(fun({label,_}) -> true; - (_) -> false end, Is0), + Is = drop_labels(Is0), index_labels_1(Is0, [{Lbl,Is}|Acc]); index_labels_1([_|Is], Acc) -> index_labels_1(Is, Acc); index_labels_1([], Acc) -> gb_trees:from_orddict(sort(Acc)). +drop_labels([{label,_}|Is]) -> drop_labels(Is); +drop_labels(Is) -> Is. + %% Help functions for combine_heap_needs. combine_alloc_lists(Al1, Al2) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 4d4536b79c..c55919dc73 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -667,7 +667,7 @@ valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) -> set_type_reg({tuple,Sz}, Tuple, branch_state(Lbl, Vst)); valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) -> assert_type(map, Src, Vst), - assert_strict_literal_termorder(List), + assert_unique_map_keys(List), branch_state(Lbl, Vst); valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) -> Vst = branch_state(Lbl, Vst0), @@ -774,7 +774,7 @@ verify_get_map(Fail, Src, List, Vst0) -> assert_type(map, Src, Vst0), Vst1 = branch_state(Fail, Vst0), Keys = extract_map_keys(List), - assert_strict_literal_termorder(Keys), + assert_unique_map_keys(Keys), verify_get_map_pair(List,Vst0,Vst1). extract_map_keys([Key,_Val|T]) -> @@ -794,6 +794,8 @@ verify_put_map(Fail, Src, Dst, Live, List, Vst0) -> Vst1 = heap_alloc(0, Vst0), Vst2 = branch_state(Fail, Vst1), Vst = prune_x_regs(Live, Vst2), + Keys = extract_map_keys(List), + assert_unique_map_keys(Keys), set_type_reg(map, Dst, Vst). %% @@ -1007,29 +1009,23 @@ assert_freg_set(Fr, _) -> error({bad_source,Fr}). %% A single item list may be either a list or a register. %% -%% A list with more than item must contain literals in -%% ascending term order. +%% A list with more than item must contain unique literals. %% %% An empty list is not allowed. -assert_strict_literal_termorder([]) -> +assert_unique_map_keys([]) -> %% There is no reason to use the get_map_elements and %% has_map_fields instructions with empty lists. error(empty_field_list); -assert_strict_literal_termorder([_]) -> +assert_unique_map_keys([_]) -> ok; -assert_strict_literal_termorder([_,_|_]=Ls) -> +assert_unique_map_keys([_,_|_]=Ls) -> Vs = [get_literal(L) || L <- Ls], - case check_strict_value_termorder(Vs) of - true -> ok; - false -> error(not_strict_order) + case length(Vs) =:= sets:size(sets:from_list(Vs)) of + true -> ok; + false -> error(keys_not_unique) end. -check_strict_value_termorder([V1|[V2|_]=Vs]) -> - erts_internal:cmp_term(V1, V2) < 0 andalso - check_strict_value_termorder(Vs); -check_strict_value_termorder([_]) -> true. - %%% %%% New binary matching instructions. %%% diff --git a/lib/compiler/src/beam_z.erl b/lib/compiler/src/beam_z.erl index 0c7bef9183..47e786034d 100644 --- a/lib/compiler/src/beam_z.erl +++ b/lib/compiler/src/beam_z.erl @@ -74,6 +74,13 @@ undo_rename({bs_init,F,{I,Extra,U,Flags},Live,[Sz,Src],Dst}) -> {I,F,Sz,Extra,Live,U,Src,Flags,Dst}; undo_rename({bs_init,_,bs_init_writable=I,_,_,_}) -> I; +undo_rename({test,bs_match_string=Op,F,[Ctx,Bin0]}) -> + Bits = bit_size(Bin0), + Bin = case Bits rem 8 of + 0 -> Bin0; + Rem -> <<Bin0/bitstring,0:(8-Rem)>> + end, + {test,Op,F,[Ctx,Bits,{string,binary_to_list(Bin)}]}; undo_rename({put_map,Fail,assoc,S,D,R,L}) -> {put_map_assoc,Fail,S,D,R,L}; undo_rename({put_map,Fail,exact,S,D,R,L}) -> diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index c45c9a1a29..5bd33c4d18 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -285,11 +285,20 @@ internal_comp(Passes, File, Suffix, St0) -> St1 = St0#compile{filename=File, dir=Dir, base=Base, ifile=erlfile(Dir, Base, Suffix), ofile=objfile(Base, St0)}, - Run = case member(time, St1#compile.options) of - true -> - io:format("Compiling ~tp\n", [File]), - fun run_tc/2; - false -> fun({_Name,Fun}, St) -> catch Fun(St) end + Opts = St1#compile.options, + Run0 = case member(time, Opts) of + true -> + io:format("Compiling ~tp\n", [File]), + fun run_tc/2; + false -> fun({_Name,Fun}, St) -> catch Fun(St) end + end, + Run = case keyfind(eprof, 1, Opts) of + {eprof,EprofPass} -> + fun(P, St) -> + run_eprof(P, EprofPass, St) + end; + false -> + Run0 end, case fold_comp(Passes, Run, St1) of {ok,St2} -> comp_ret_ok(St2); @@ -320,17 +329,26 @@ fold_comp([{Name,Pass}|Ps], Run, St0) -> fold_comp([], _Run, St) -> {ok,St}. run_tc({Name,Fun}, St) -> - Before0 = statistics(runtime), + T1 = erlang:monotonic_time(), Val = (catch Fun(St)), - After0 = statistics(runtime), - {Before_c, _} = Before0, - {After_c, _} = After0, + T2 = erlang:monotonic_time(), + Elapsed = erlang:convert_time_unit(T2 - T1, native, milli_seconds), Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize), Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])), - io:format(" ~-30s: ~10.2f s ~12s\n", - [Name,(After_c-Before_c) / 1000,Mem]), + io:format(" ~-30s: ~10.3f s ~12s\n", + [Name,Elapsed/1000,Mem]), Val. +run_eprof({Name,Fun}, Name, St) -> + io:format("~p: Running eprof\n", [Name]), + eprof:start_profiling([self()]), + Val = (catch Fun(St)), + eprof:stop_profiling(), + eprof:analyze(), + Val; +run_eprof({_,Fun}, _, St) -> + catch Fun(St). + comp_ret_ok(#compile{code=Code,warnings=Warn0,module=Mod,options=Opts}=St) -> case werror(St) of true -> @@ -606,7 +624,7 @@ standard_passes() -> {iff,'to_exp',{done,"E"}}, %% Conversion to Core Erlang. - ?pass(core_module), + {pass,v3_core}, {iff,'dcore',{listing,"core"}}, {iff,'to_core0',{done,"core"}} | core_passes()]. @@ -618,7 +636,7 @@ core_passes() -> [{unless,no_copt, [{core_old_inliner,fun test_old_inliner/1,fun core_old_inliner/1}, {iff,doldinline,{listing,"oldinline"}}, - ?pass(core_fold_module), + {pass,sys_core_fold}, {iff,dcorefold,{listing,"corefold"}}, {core_inline_module,fun test_core_inliner/1,fun core_inline_module/1}, {iff,dinline,{listing,"inline"}}, @@ -631,14 +649,14 @@ core_passes() -> kernel_passes() -> %% Destructive setelement/3 optimization and core lint. - [?pass(core_dsetel_module), + [{pass,sys_core_dsetel}, {iff,dsetel,{listing,"dsetel"}}, {iff,clint,?pass(core_lint_module)}, {iff,core,?pass(save_core_code)}, %% Kernel Erlang and code generation. - ?pass(kernel_module), + {pass,v3_kernel}, {iff,dkern,{listing,"kernel"}}, {iff,'to_kernel',{done,"kernel"}}, {pass,v3_life}, @@ -1176,14 +1194,6 @@ expand_module(#compile{code=Code,options=Opts0}=St0) -> Opts = expand_opts(Opts1), {ok,St0#compile{module=Mod,options=Opts,code={Mod,Exp,Forms}}}. -core_module(#compile{code=Code0,options=Opts}=St) -> - {ok,Code,Ws} = v3_core:module(Code0, Opts), - {ok,St#compile{code=Code,warnings=St#compile.warnings ++ Ws}}. - -core_fold_module(#compile{code=Code0,options=Opts,warnings=Warns}=St) -> - {ok,Code,Ws} = sys_core_fold:module(Code0, Opts), - {ok,St#compile{code=Code,warnings=Warns ++ Ws}}. - core_fold_module_after_inlining(#compile{code=Code0,options=Opts}=St) -> %% Inlining may produce code that generates spurious warnings. %% Ignore all warnings. @@ -1219,14 +1229,6 @@ core_inline_module(#compile{code=Code0,options=Opts}=St) -> Code = cerl_inline:core_transform(Code0, Opts), {ok,St#compile{code=Code}}. -core_dsetel_module(#compile{code=Code0,options=Opts}=St) -> - {ok,Code} = sys_core_dsetel:module(Code0, Opts), - {ok,St#compile{code=Code}}. - -kernel_module(#compile{code=Code0,options=Opts}=St) -> - {ok,Code,Ws} = v3_kernel:module(Code0, Opts), - {ok,St#compile{code=Code,warnings=St#compile.warnings ++ Ws}}. - save_abstract_code(#compile{ifile=File}=St) -> case abstract_code(St) of {ok,Code} -> diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 0d020578f5..beab2ce897 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -70,7 +70,8 @@ -export([module/2,format_error/1]). -import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,all/2,any/2, - reverse/1,reverse/2,member/2,nth/2,flatten/1,unzip/1]). + reverse/1,reverse/2,member/2,nth/2,flatten/1, + unzip/1,keyfind/3]). -import(cerl, [ann_c_cons/3,ann_c_map/3,ann_c_tuple/2]). @@ -1624,12 +1625,11 @@ eval_case(Case, _) -> Case. eval_case_warn(#c_primop{anno=Anno, name=#c_literal{val=match_fail}, - args=[#c_literal{val=Reason}]}=Core) - when is_atom(Reason) -> - case member(eval_failure, Anno) of + args=[_]}=Core) -> + case keyfind(eval_failure, 1, Anno) of false -> ok; - true -> + {eval_failure,Reason} -> %% Example: M = not_map, M#{k:=v} add_warning(Core, {eval_failure,Reason}) end; diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl index 7eec9dd62b..15a54a5886 100644 --- a/lib/compiler/src/v3_codegen.erl +++ b/lib/compiler/src/v3_codegen.erl @@ -121,24 +121,15 @@ cg_fun(Les, Hvs, Vdb, AtomMod, NameArity, Anno, St0) -> put_reg(V, Reg) end, [], Hvs), stk=[]}, 0, Vdb), - {B0,_Aft,St} = cg_list(Les, 0, Vdb, Bef, + {B,_Aft,St} = cg_list(Les, 0, Vdb, Bef, St3#cg{bfail=0, ultimate_failure=UltimateMatchFail, is_top_block=true}), - B = fix_bs_match_strings(B0), {Name,Arity} = NameArity, Asm = [{label,Fi},line(Anno),{func_info,AtomMod,{atom,Name},Arity}, {label,Fl}|B++[{label,UltimateMatchFail},if_end]], {Asm,Fl,St}. -fix_bs_match_strings([{test,bs_match_string,F,[Ctx,BinList]}|Is]) - when is_list(BinList) -> - I = {test,bs_match_string,F,[Ctx,list_to_bitstring(BinList)]}, - [I|fix_bs_match_strings(Is)]; -fix_bs_match_strings([I|Is]) -> - [I|fix_bs_match_strings(Is)]; -fix_bs_match_strings([]) -> []. - %% cg(Lkexpr, Vdb, StackReg, State) -> {[Ainstr],StackReg,State}. %% Generate code for a kexpr. %% Split function into two steps for clarity, not efficiency. @@ -584,7 +575,7 @@ top_level_block(Keis, Bef, MaxRegs, _St) -> (return) -> [{deallocate,FrameSz},return]; (Tuple) when is_tuple(Tuple) -> - [turn_yregs(tuple_size(Tuple), Tuple, MaxY)]; + [turn_yregs(Tuple, MaxY)]; (Other) -> [Other] end, Keis), @@ -596,14 +587,49 @@ top_level_block(Keis, Bef, MaxRegs, _St) -> %% catches work. The code generation algorithm gives a lower register %% number to the outer catch, which is wrong. -turn_yregs(0, Tp, _) -> Tp; -turn_yregs(El, Tp, MaxY) -> - turn_yregs(El-1,setelement(El,Tp,turn_yreg(element(El,Tp),MaxY)),MaxY). - -turn_yreg({yy,YY},MaxY) -> {y,MaxY-YY}; -turn_yreg({list,Ls},MaxY) -> {list, turn_yreg(Ls,MaxY)}; -turn_yreg(Ts,MaxY) when is_list(Ts) -> [turn_yreg(T,MaxY)||T<-Ts]; -turn_yreg(Other,_MaxY) -> Other. +turn_yregs({call,_,_}=I, _MaxY) -> I; +turn_yregs({call_ext,_,_}=I, _MaxY) -> I; +turn_yregs({jump,_}=I, _MaxY) -> I; +turn_yregs({label,_}=I, _MaxY) -> I; +turn_yregs({line,_}=I, _MaxY) -> I; +turn_yregs({test_heap,_,_}=I, _MaxY) -> I; +turn_yregs({bif,Op,F,A,B}, MaxY) -> + {bif,Op,F,turn_yreg(A, MaxY),turn_yreg(B, MaxY)}; +turn_yregs({gc_bif,Op,F,Live,A,B}, MaxY) when is_integer(Live) -> + {gc_bif,Op,F,Live,turn_yreg(A, MaxY),turn_yreg(B, MaxY)}; +turn_yregs({get_tuple_element,S,N,D}, MaxY) -> + {get_tuple_element,turn_yreg(S, MaxY),N,turn_yreg(D, MaxY)}; +turn_yregs({put_tuple,Arity,D}, MaxY) -> + {put_tuple,Arity,turn_yreg(D, MaxY)}; +turn_yregs({select_val,R,F,L}, MaxY) -> + {select_val,turn_yreg(R, MaxY),F,L}; +turn_yregs({test,Op,F,L}, MaxY) -> + {test,Op,F,turn_yreg(L, MaxY)}; +turn_yregs({test,Op,F,Live,A,B}, MaxY) when is_integer(Live) -> + {test,Op,F,Live,turn_yreg(A, MaxY),turn_yreg(B, MaxY)}; +turn_yregs({Op,A}, MaxY) -> + {Op,turn_yreg(A, MaxY)}; +turn_yregs({Op,A,B}, MaxY) -> + {Op,turn_yreg(A, MaxY),turn_yreg(B, MaxY)}; +turn_yregs({Op,A,B,C}, MaxY) -> + {Op,turn_yreg(A, MaxY),turn_yreg(B, MaxY),turn_yreg(C, MaxY)}; +turn_yregs(Tuple, MaxY) -> + turn_yregs(tuple_size(Tuple), Tuple, MaxY). + +turn_yregs(1, Tp, _) -> + Tp; +turn_yregs(N, Tp, MaxY) -> + E = turn_yreg(element(N, Tp), MaxY), + turn_yregs(N-1, setelement(N, Tp, E), MaxY). + +turn_yreg({yy,YY}, MaxY) -> + {y,MaxY-YY}; +turn_yreg({list,Ls},MaxY) -> + {list,turn_yreg(Ls, MaxY)}; +turn_yreg([_|_]=Ts, MaxY) -> + [turn_yreg(T, MaxY) || T <- Ts]; +turn_yreg(Other, _MaxY) -> + Other. %% select_cg(Sclause, V, TypeFail, ValueFail, StackReg, State) -> %% {Is,StackReg,State}. @@ -682,22 +708,37 @@ select_nil(#l{ke={val_clause,nil,B}}, V, Tf, Vf, Bef, St0) -> select_binary(#l{ke={val_clause,{binary,{var,V}},B},i=I,vdb=Vdb}, V, Tf, Vf, Bef, St0) -> Int0 = clear_dead(Bef#sr{reg=Bef#sr.reg}, I, Vdb), - {Bis,Aft,St1} = match_cg(B, Vf, Int0, St0), + {Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0), CtxReg = fetch_var(V, Int0), Live = max_reg(Bef#sr.reg), - {[{test,bs_start_match2,{f,Tf},Live,[CtxReg,V],CtxReg}, - {bs_save2,CtxReg,{V,V}}|Bis], - Aft,St1}; + Bis1 = [{test,bs_start_match2,{f,Tf},Live,[CtxReg,V],CtxReg}, + {bs_save2,CtxReg,{V,V}}|Bis0], + Bis = finish_select_binary(Bis1), + {Bis,Aft,St1}; select_binary(#l{ke={val_clause,{binary,{var,Ivar}},B},i=I,vdb=Vdb}, V, Tf, Vf, Bef, St0) -> Regs = put_reg(Ivar, Bef#sr.reg), Int0 = clear_dead(Bef#sr{reg=Regs}, I, Vdb), - {Bis,Aft,St1} = match_cg(B, Vf, Int0, St0), + {Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0), CtxReg = fetch_var(Ivar, Int0), Live = max_reg(Bef#sr.reg), - {[{test,bs_start_match2,{f,Tf},Live,[fetch_var(V, Bef),Ivar],CtxReg}, - {bs_save2,CtxReg,{Ivar,Ivar}}|Bis], - Aft,St1}. + Bis1 = [{test,bs_start_match2,{f,Tf},Live,[fetch_var(V, Bef),Ivar],CtxReg}, + {bs_save2,CtxReg,{Ivar,Ivar}}|Bis0], + Bis = finish_select_binary(Bis1), + {Bis,Aft,St1}. + +finish_select_binary([{bs_save2,R,Point}=I,{bs_restore2,R,Point}|Is]) -> + [I|finish_select_binary(Is)]; +finish_select_binary([{bs_save2,R,Point}=I,{test,is_eq_exact,_,_}=Test, + {bs_restore2,R,Point}|Is]) -> + [I,Test|finish_select_binary(Is)]; +finish_select_binary([{test,bs_match_string,F,[Ctx,BinList]}|Is]) + when is_list(BinList) -> + I = {test,bs_match_string,F,[Ctx,list_to_bitstring(BinList)]}, + [I|finish_select_binary(Is)]; +finish_select_binary([I|Is]) -> + [I|finish_select_binary(Is)]; +finish_select_binary([]) -> []. %% New instructions for selection of binary segments. @@ -1549,11 +1590,8 @@ set_cg([{var,R}], {map,Op,Map,Es}, Le, Vdb, Bef, SrcReg = cg_reg_arg(Map,Int0), Line = line(Le#l.a), - %% The instruction needs to store keys in term sorted order - %% All keys has to be unique here - Pairs = map_pair_strip_and_termsort(Es), - %% fetch registers for values to be put into the map + Pairs = [{K,V} || {_,K,V} <- Es], List = flatmap(fun({K,V}) -> [K,cg_reg_arg(V,Int0)] end, Pairs), Live = max_reg(Bef#sr.reg), @@ -1580,16 +1618,6 @@ set_cg([{var,R}], Con, Le, Vdb, Bef, St) -> end, {Ais,clear_dead(Int, Le#l.i, Vdb),St}. -map_pair_strip_and_termsort(Es) -> - %% format in - %% [{map_pair,K,V}] - %% where K is for example {integer, 1} and we want to sort on 1. - Ls = [{K,V}||{_,K,V}<-Es], - lists:sort(fun ({{_,A},_}, {{_,B},_}) -> erts_internal:cmp_term(A,B) =< 0; - ({nil,_}, {{_,B},_}) -> [] =< B; - ({{_,A},_}, {nil,_}) -> A =< [] - end, Ls). - %%% %%% Code generation for constructing binaries. %%% diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index c954d21e59..ed7b55df07 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -538,19 +538,8 @@ expr({tuple,L,Es0}, St0) -> {annotate_tuple(A, Es1, St1),Eps,St1}; expr({map,L,Es0}, St0) -> map_build_pairs(#c_literal{val=#{}}, Es0, full_anno(L, St0), St0); -expr({map,L,M0,Es0}, St0) -> - try expr_map(M0,Es0,lineno_anno(L, St0),St0) of - {_,_,_}=Res -> Res - catch - throw:{bad_map,Warning} -> - St = add_warning(L, Warning, St0), - LineAnno = lineno_anno(L, St), - As = [#c_literal{anno=LineAnno,val=badarg}], - {#icall{anno=#a{anno=LineAnno}, %Must have an #a{} - module=#c_literal{anno=LineAnno,val=erlang}, - name=#c_literal{anno=LineAnno,val=error}, - args=As},[],St} - end; +expr({map,L,M,Es}, St) -> + expr_map(M, Es, L, St); expr({bin,L,Es0}, St0) -> try expr_bin(Es0, full_anno(L, St0), St0) of {_,_,_}=Res -> Res @@ -758,11 +747,14 @@ make_bool_switch_guard(L, E, V, T, F) -> {clause,NegL,[V],[],[V]} ]}. -expr_map(M0, Es0, A, St0) -> +expr_map(M0, Es0, L, St0) -> {M1,Eps0,St1} = safe(M0, St0), + Badmap = badmap_term(M1, St1), + A = lineno_anno(L, St1), + Fc = fail_clause([], [{eval_failure,badmap}|A], Badmap), case is_valid_map_src(M1) of true -> - {M2,Eps1,St2} = map_build_pairs(M1, Es0, A, St1), + {M2,Eps1,St2} = map_build_pairs(M1, Es0, full_anno(L, St1), St1), M3 = case Es0 of [] -> M1; [_|_] -> M2 @@ -775,13 +767,23 @@ expr_map(M0, Es0, A, St0) -> name=#c_literal{anno=A,val=is_map}, args=[M1]}], body=[M3]}], - Fc = fail_clause([], [eval_failure|A], #c_literal{val=badarg}), Eps = Eps0 ++ Eps1, {#icase{anno=#a{anno=A},args=[],clauses=Cs,fc=Fc},Eps,St2}; false -> - throw({bad_map,bad_map}) + %% Not a map source. The update will always fail. + St2 = add_warning(L, badmap, St1), + #iclause{body=[Fail]} = Fc, + {Fail,Eps0,St2} end. +badmap_term(_Map, #core{in_guard=true}) -> + %% The code generator cannot handle complex error reasons + %% in guards. But the exact error reason does not matter anyway + %% since it is not user-visible. + #c_literal{val=badmap}; +badmap_term(Map, #core{in_guard=false}) -> + #c_tuple{es=[#c_literal{val=badmap},Map]}. + map_build_pairs(Map, Es0, Ann, St0) -> {Es,Pre,St1} = map_build_pairs_1(Es0, St0), {ann_c_map(Ann, Map, Es),Pre,St1}. @@ -2395,7 +2397,7 @@ format_error(nomatch) -> "pattern cannot possibly match"; format_error(bad_binary) -> "binary construction will fail because of a type mismatch"; -format_error(bad_map) -> +format_error(badmap) -> "map construction will fail because of a type mismatch". add_warning(Line, Term, #core{ws=Ws,file=[{file,File}]}=St) when Line >= 0 -> diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index 0ac1aaf158..7dff58582e 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -836,12 +836,23 @@ get_vsub(V, Vsub) -> set_vsub(V, S, Vsub) -> orddict:store(V, S, Vsub). -subst_vsub(V, S, Vsub0) -> - %% Fold chained substitutions. - Vsub1 = orddict:map(fun (_, V1) when V1 =:= V -> S; - (_, V1) -> V1 - end, Vsub0), - orddict:store(V, S, Vsub1). +subst_vsub(Key, New, [{K,Key}|Dict]) -> + %% Fold chained substitution. + [{K,New}|subst_vsub(Key, New, Dict)]; +subst_vsub(Key, New, [{K,_}|_]=Dict) when Key < K -> + %% Insert the new substitution here, and continue + %% look for chained substitutions. + [{Key,New}|subst_vsub_1(Key, New, Dict)]; +subst_vsub(Key, New, [{K,_}=E|Dict]) when Key > K -> + [E|subst_vsub(Key, New, Dict)]; +subst_vsub(Key, New, []) -> [{Key,New}]. + +subst_vsub_1(V, S, [{K,V}|Dict]) -> + %% Fold chained substitution. + [{K,S}|subst_vsub_1(V, S, Dict)]; +subst_vsub_1(V, S, [E|Dict]) -> + [E|subst_vsub_1(V, S, Dict)]; +subst_vsub_1(_, _, []) -> []. get_fsub(F, A, Fsub) -> case orddict:find({F,A}, Fsub) of diff --git a/lib/compiler/src/v3_life.erl b/lib/compiler/src/v3_life.erl index 75bd188479..4b1f1c3f71 100644 --- a/lib/compiler/src/v3_life.erl +++ b/lib/compiler/src/v3_life.erl @@ -45,7 +45,7 @@ -export([vdb_find/2]). --import(lists, [member/2,map/2,foldl/3,reverse/1,sort/1]). +-import(lists, [member/2,map/2,reverse/1,sort/1]). -import(ordsets, [add_element/2,intersection/2,union/2]). -include("v3_kernel.hrl"). @@ -68,7 +68,7 @@ functions([], Acc) -> reverse(Acc). function(#k_fdef{anno=#k{a=Anno},func=F,arity=Ar,vars=Vs,body=Kb}) -> try As = var_list(Vs), - Vdb0 = foldl(fun ({var,N}, Vdb) -> new_var(N, 0, Vdb) end, [], As), + Vdb0 = init_vars(As), %% Force a top-level match! B0 = case Kb of #k_match{} -> Kb; @@ -94,14 +94,14 @@ function(#k_fdef{anno=#k{a=Anno},func=F,arity=Ar,vars=Vs,body=Kb}) -> body(#k_seq{arg=Ke,body=Kb}, I, Vdb0) -> %%ok = io:fwrite("life ~w:~p~n", [?LINE,{Ke,I,Vdb0}]), A = get_kanno(Ke), - Vdb1 = use_vars(A#k.us, I, new_vars(A#k.ns, I, Vdb0)), + Vdb1 = use_vars(union(A#k.us, A#k.ns), I, Vdb0), {Es,MaxI,Vdb2} = body(Kb, I+1, Vdb1), E = expr(Ke, I, Vdb2), {[E|Es],MaxI,Vdb2}; body(Ke, I, Vdb0) -> %%ok = io:fwrite("life ~w:~p~n", [?LINE,{Ke,I,Vdb0}]), A = get_kanno(Ke), - Vdb1 = use_vars(A#k.us, I, new_vars(A#k.ns, I, Vdb0)), + Vdb1 = use_vars(union(A#k.us, A#k.ns), I, Vdb0), E = expr(Ke, I, Vdb1), {[E],I,Vdb1}. @@ -150,12 +150,12 @@ expr(#k_try_enter{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh}, I, Vdb) - %% the body and handler. Add try tag 'variable'. Ab = get_kanno(Kb), Ah = get_kanno(Kh), - Tdb1 = use_vars(Ab#k.us, I+3, use_vars(Ah#k.us, I+3, Tdb0)), + Tdb1 = use_vars(union(Ab#k.us, Ah#k.us), I+3, Tdb0), Tdb2 = vdb_sub(I, I+2, Tdb1), Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, 1000000, Tdb2)), - {Bes,_,Bdb} = body(Kb, I+4, new_vars(map(Vnames, Vs), I+3, Tdb2)), - {Hes,_,Hdb} = body(Kh, I+4, new_vars(map(Vnames, Evs), I+3, Tdb2)), + {Bes,_,Bdb} = body(Kb, I+4, new_vars(sort(map(Vnames, Vs)), I+3, Tdb2)), + {Hes,_,Hdb} = body(Kh, I+4, new_vars(sort(map(Vnames, Evs)), I+3, Tdb2)), #l{ke={try_enter,#l{ke={block,Aes},i=I+1,vdb=Adb,a=[]}, var_list(Vs),#l{ke={block,Bes},i=I+3,vdb=Bdb,a=[]}, var_list(Evs),#l{ke={block,Hes},i=I+3,vdb=Hdb,a=[]}}, @@ -171,7 +171,7 @@ expr(#k_receive{anno=A,var=V,body=Kb,timeout=T,action=Ka,ret=Rs}, I, Vdb) -> %% Work out imported variables which need to be locked. Rdb = vdb_sub(I, I+1, Vdb), M = match(Kb, add_element(V#k_var.name, A#k.us), I+1, [], - new_var(V#k_var.name, I, Rdb)), + new_vars([V#k_var.name], I, Rdb)), {Tes,_,Adb} = body(Ka, I+1, Rdb), #l{ke={receive_loop,atomic(T),variable(V),M, #l{ke=Tes,i=I+1,vdb=Adb,a=[]},var_list(Rs)}, @@ -199,12 +199,12 @@ body_try(#k_try{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh,ret=Rs}, %% the body and handler. Add try tag 'variable'. Ab = get_kanno(Kb), Ah = get_kanno(Kh), - Tdb1 = use_vars(Ab#k.us, I+3, use_vars(Ah#k.us, I+3, Tdb0)), + Tdb1 = use_vars(union(Ab#k.us, Ah#k.us), I+3, Tdb0), Tdb2 = vdb_sub(I, I+2, Tdb1), Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, locked, Tdb2)), - {Bes,_,Bdb} = body(Kb, I+4, new_vars(map(Vnames, Vs), I+3, Tdb2)), - {Hes,_,Hdb} = body(Kh, I+4, new_vars(map(Vnames, Evs), I+3, Tdb2)), + {Bes,_,Bdb} = body(Kb, I+4, new_vars(sort(map(Vnames, Vs)), I+3, Tdb2)), + {Hes,_,Hdb} = body(Kh, I+4, new_vars(sort(map(Vnames, Evs)), I+3, Tdb2)), #l{ke={'try',#l{ke={block,Aes},i=I+1,vdb=Adb,a=[]}, var_list(Vs),#l{ke={block,Bes},i=I+3,vdb=Bdb,a=[]}, var_list(Evs),#l{ke={block,Hes},i=I+3,vdb=Hdb,a=[]}, @@ -400,79 +400,68 @@ is_gc_bif(Bif, Arity) -> erl_internal:new_type_test(Bif, Arity) orelse erl_internal:comp_op(Bif, Arity)). -%% new_var(VarName, I, Vdb) -> Vdb. +%% Keep track of life time for variables. +%% +%% init_vars([{var,VarName}]) -> Vdb. %% new_vars([VarName], I, Vdb) -> Vdb. -%% use_var(VarName, I, Vdb) -> Vdb. %% use_vars([VarName], I, Vdb) -> Vdb. %% add_var(VarName, F, L, Vdb) -> Vdb. +%% +%% The list of variable names for new_vars/3 and use_vars/3 +%% must be sorted. -new_var(V, I, Vdb) -> - vdb_store_new(V, I, I, Vdb). +init_vars(Vs) -> + sort([{V,0,0} || {var,V} <- Vs]). -new_vars(Vs, I, Vdb0) -> - foldl(fun (V, Vdb) -> new_var(V, I, Vdb) end, Vdb0, Vs). +new_vars([], _, Vdb) -> Vdb; +new_vars([V], I, Vdb) -> vdb_store_new(V, {V,I,I}, Vdb); +new_vars(Vs, I, Vdb) -> vdb_update_vars(Vs, Vdb, I). -use_var(V, I, Vdb) -> +use_vars([], _, Vdb) -> + Vdb; +use_vars([V], I, Vdb) -> case vdb_find(V, Vdb) of - {V,F,L} when I > L -> vdb_update(V, F, I, Vdb); + {V,F,L} when I > L -> vdb_update(V, {V,F,I}, Vdb); {V,_,_} -> Vdb; - error -> vdb_store_new(V, I, I, Vdb) - end. - -use_vars([], _, Vdb) -> Vdb; -use_vars([V], I, Vdb) -> use_var(V, I, Vdb); -use_vars(Vs, I, Vdb) -> - Res = use_vars_1(sort(Vs), Vdb, I), - %% The following line can be used as an assertion. - %% Res = foldl(fun (V, Vdb) -> use_var(V, I, Vdb) end, Vdb, Vs), - Res. - -%% Measurements show that it is worthwhile having this special -%% function that updates/inserts several variables at once. - -use_vars_1([V|_]=Vs, [{V1,_,_}=Vd|Vdb], I) when V > V1 -> - [Vd|use_vars_1(Vs, Vdb, I)]; -use_vars_1([V|Vs], [{V1,_,_}|_]=Vdb, I) when V < V1 -> - %% New variable. - [{V,I,I}|use_vars_1(Vs, Vdb, I)]; -use_vars_1([V|Vs], [{_,F,L}=Vd|Vdb], I) -> - %% Existing variable. - if - I > L ->[{V,F,I}|use_vars_1(Vs, Vdb, I)]; - true -> [Vd|use_vars_1(Vs, Vdb, I)] + error -> vdb_store_new(V, {V,I,I}, Vdb) end; -use_vars_1([V|Vs], [], I) -> - %% New variable. - [{V,I,I}|use_vars_1(Vs, [], I)]; -use_vars_1([], Vdb, _) -> Vdb. +use_vars(Vs, I, Vdb) -> vdb_update_vars(Vs, Vdb, I). add_var(V, F, L, Vdb) -> - vdb_store_new(V, F, L, Vdb). + vdb_store_new(V, {V,F,L}, Vdb). vdb_find(V, Vdb) -> - %% Performance note: Profiling shows that this function accounts for - %% a lot of the execution time when huge constant terms are built. - %% Using the BIF lists:keyfind/3 is a lot faster than the - %% original Erlang version. case lists:keyfind(V, 1, Vdb) of false -> error; Vd -> Vd end. -%vdb_find(V, [{V1,F,L}=Vd|Vdb]) when V < V1 -> error; -%vdb_find(V, [{V1,F,L}=Vd|Vdb]) when V == V1 -> Vd; -%vdb_find(V, [{V1,F,L}=Vd|Vdb]) when V > V1 -> vdb_find(V, Vdb); -%vdb_find(V, []) -> error. +vdb_update(V, Update, [{V,_,_}|Vdb]) -> + [Update|Vdb]; +vdb_update(V, Update, [Vd|Vdb]) -> + [Vd|vdb_update(V, Update, Vdb)]. -vdb_update(V, F, L, [{V1,_,_}=Vd|Vdb]) when V > V1 -> - [Vd|vdb_update(V, F, L, Vdb)]; -vdb_update(V, F, L, [{V1,_,_}|Vdb]) when V == V1 -> - [{V,F,L}|Vdb]. +vdb_store_new(V, New, [{V1,_,_}=Vd|Vdb]) when V > V1 -> + [Vd|vdb_store_new(V, New, Vdb)]; +vdb_store_new(V, New, [{V1,_,_}|_]=Vdb) when V < V1 -> + [New|Vdb]; +vdb_store_new(_, New, []) -> [New]. -vdb_store_new(V, F, L, [{V1,_,_}=Vd|Vdb]) when V > V1 -> - [Vd|vdb_store_new(V, F, L, Vdb)]; -vdb_store_new(V, F, L, [{V1,_,_}|_]=Vdb) when V < V1 -> [{V,F,L}|Vdb]; -vdb_store_new(V, F, L, []) -> [{V,F,L}]. +vdb_update_vars([V|_]=Vs, [{V1,_,_}=Vd|Vdb], I) when V > V1 -> + [Vd|vdb_update_vars(Vs, Vdb, I)]; +vdb_update_vars([V|Vs], [{V1,_,_}|_]=Vdb, I) when V < V1 -> + %% New variable. + [{V,I,I}|vdb_update_vars(Vs, Vdb, I)]; +vdb_update_vars([V|Vs], [{_,F,L}=Vd|Vdb], I) -> + %% Existing variable. + if + I > L -> [{V,F,I}|vdb_update_vars(Vs, Vdb, I)]; + true -> [Vd|vdb_update_vars(Vs, Vdb, I)] + end; +vdb_update_vars([V|Vs], [], I) -> + %% New variable. + [{V,I,I}|vdb_update_vars(Vs, [], I)]; +vdb_update_vars([], Vdb, _) -> Vdb. %% vdb_sub(Min, Max, Vdb) -> Vdb. %% Extract variables which are used before and after Min. Lock diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 73d52a48bc..98125fc84e 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -35,6 +35,7 @@ MODULES= \ record_SUITE \ trycatch_SUITE \ warnings_SUITE \ + z_SUITE \ test_lib NO_OPT= \ @@ -79,12 +80,6 @@ INLINE= \ receive \ record -CORE_MODULES = \ - bs_shadowed_size_var \ - unused_multiple_values_error \ - nested_call_in_case - - NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE) NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl) POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE) @@ -94,8 +89,6 @@ INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) -CORE_FILES= $(CORE_MODULES:%=%.core) - ##TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) ##INSTALL_PROGS= $(TARGET_FILES) @@ -162,7 +155,7 @@ release_spec: opt release_tests_spec: make_emakefile $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) compiler.spec compiler.cover \ - $(EMAKEFILE) $(ERL_FILES) $(CORE_FILES) "$(RELSYSDIR)" + $(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ $(INLINE_ERL_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 1b1c7db0e8..551cf7661b 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -420,9 +420,9 @@ map_field_lists(Config) -> Errors = do_val(map_field_lists, Config), [{{map_field_lists,x,1}, {{test,has_map_fields,{f,1},{x,0}, - {list,[{atom,z},{atom,a}]}}, + {list,[{atom,a},{atom,a}]}}, 5, - not_strict_order}}, + keys_not_unique}}, {{map_field_lists,y,1}, {{test,has_map_fields,{f,3},{x,0},{list,[]}}, 5, diff --git a/lib/compiler/test/beam_validator_SUITE_data/map_field_lists.S b/lib/compiler/test/beam_validator_SUITE_data/map_field_lists.S index 9af68c82d4..5e7ccc1e5d 100644 --- a/lib/compiler/test/beam_validator_SUITE_data/map_field_lists.S +++ b/lib/compiler/test/beam_validator_SUITE_data/map_field_lists.S @@ -13,7 +13,7 @@ {func_info,{atom,map_field_lists},{atom,x},1}. {label,2}. {test,is_map,{f,1},[{x,0}]}. - {test,has_map_fields,{f,1},{x,0},{list,[{atom,z},{atom,a}]}}. + {test,has_map_fields,{f,1},{x,0},{list,[{atom,a},{atom,a}]}}. {move,{atom,ok},{x,0}}. return. diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index f7af56afcc..b54db06339 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -24,7 +24,7 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, fun_shadow/1,int_float/1,otp_5269/1,null_fields/1,wiger/1, - bin_tail/1,save_restore/1,shadowed_size_var/1, + bin_tail/1,save_restore/1, partitioned_bs_match/1,function_clause/1, unit/1,shared_sub_bins/1,bin_and_float/1, dec_subidentifiers/1,skip_optional_tag/1, @@ -34,7 +34,8 @@ otp_7188/1,otp_7233/1,otp_7240/1,otp_7498/1, match_string/1,zero_width/1,bad_size/1,haystack/1, cover_beam_bool/1,matched_out_size/1,follow_fail_branch/1, - no_partition/1,calling_a_binary/1,binary_in_map/1]). + no_partition/1,calling_a_binary/1,binary_in_map/1, + match_string_opt/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -50,7 +51,7 @@ all() -> groups() -> [{p,[parallel], [fun_shadow,int_float,otp_5269,null_fields,wiger, - bin_tail,save_restore,shadowed_size_var, + bin_tail,save_restore, partitioned_bs_match,function_clause,unit, shared_sub_bins,bin_and_float,dec_subidentifiers, skip_optional_tag,wfbm,degenerated_match,bs_sum, @@ -59,7 +60,8 @@ groups() -> matching_and_andalso,otp_7188,otp_7233,otp_7240, otp_7498,match_string,zero_width,bad_size,haystack, cover_beam_bool,matched_out_size,follow_fail_branch, - no_partition,calling_a_binary,binary_in_map]}]. + no_partition,calling_a_binary,binary_in_map, + match_string_opt]}]. init_per_suite(Config) -> @@ -322,16 +324,6 @@ bad_float_unpack_match(<<F:64/float>>) -> F; bad_float_unpack_match(<<I:64/integer-signed>>) -> I. -shadowed_size_var(Config) when is_list(Config) -> - ?line PrivDir = ?config(priv_dir, Config), - ?line Dir = filename:dirname(code:which(?MODULE)), - ?line Core = filename:join(Dir, "bs_shadowed_size_var"), - ?line Opts = [from_core,{outdir,PrivDir}|test_lib:opt_opts(?MODULE)], - ?line io:format("~p", [Opts]), - ?line {ok,Mod} = c:c(Core, Opts), - ?line [42|<<"abcde">>] = Mod:filter_essentials([<<42:32>>|<<5:32,"abcde">>]), - ok. - partitioned_bs_match(Config) when is_list(Config) -> ?line <<1,2,3>> = partitioned_bs_match(blurf, <<42,1,2,3>>), ?line error = partitioned_bs_match(10, <<7,8,15,13>>), @@ -1224,6 +1216,14 @@ match_binary_in_map(Map) -> ok end. +match_string_opt(Config) when is_list(Config) -> + {x,<<1,2,3>>,{<<1>>,{v,<<1,2,3>>}}} = + do_match_string_opt({<<1>>,{v,<<1,2,3>>}}), + ok. + +do_match_string_opt({<<1>>,{v,V}}=T) -> + {x,V,T}. + check(F, R) -> R = F(). diff --git a/lib/compiler/test/bs_shadowed_size_var.core b/lib/compiler/test/bs_shadowed_size_var.core deleted file mode 100644 index d1d5ebba6d..0000000000 --- a/lib/compiler/test/bs_shadowed_size_var.core +++ /dev/null @@ -1,25 +0,0 @@ -module 'bs_shadowed_size_var' ['filter_essentials'/1] - attributes [] - -%% Reduced code from beam_asm inlined using the old inliner. - -'filter_essentials'/1 = - fun (_cor0) -> - case _cor0 of - <[#{#<Sz>(32,1,'integer',['unsigned','big']) }#|T]> when 'true' -> - let <_cor4> = - case T of - %% Variable 'Sz' repeated here. Should work. - <#{#<Sz>(32,1,'integer',['unsigned','big']), - #<Data>(Sz,8,'binary',['unsigned','big'])}#> when 'true' -> - Data - <_cor5> when 'true' -> - primop 'match_fail' - ({'case_clause',{_cor5}}) - end - in [Sz|_cor4] - <_cor5> when 'true' -> - primop 'match_fail' - ({'function_clause',_cor5}) - end -end diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl index 296774e083..51e1da2cb6 100644 --- a/lib/compiler/test/compilation_SUITE.erl +++ b/lib/compiler/test/compilation_SUITE.erl @@ -309,8 +309,8 @@ load_and_call(Out, Module) -> %% Smoke-test of beam disassembler. ?line test_lib:smoke_disasm(Module), - ?line true = erlang:delete_module(Module), - ?line true = erlang:purge_module(Module), + _ = code:delete(Module), + _ = code:purge(Module), %% Restore state of trap_exit just in case. (Since the compiler %% uses a temporary process, we will get {'EXIT',Pid,normal} messages diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 1c96abe017..6d4fde662b 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -102,6 +102,8 @@ file_1(Config) when is_list(Config) -> ?line compile_and_verify(Simple, Target, [debug_info]), ?line {ok,simple} = compile:file(Simple, [no_line_info]), %Coverage + {ok,simple} = compile:file(Simple, [{eprof,beam_z}]), %Coverage + ?line ok = file:set_cwd(Cwd), ?line true = exists(Target), ?line passed = run(Target, test, []), diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl index 428ad65364..c4a7efbfc4 100644 --- a/lib/compiler/test/core_SUITE.erl +++ b/lib/compiler/test/core_SUITE.erl @@ -24,7 +24,9 @@ dehydrated_itracer/1,nested_tries/1, seq_in_guard/1,make_effect_seq/1,eval_is_boolean/1, unsafe_case/1,nomatch_shadow/1,reversed_annos/1, - map_core_test/1,eval_case/1,bad_boolean_guard/1]). + map_core_test/1,eval_case/1,bad_boolean_guard/1, + bs_shadowed_size_var/1 + ]). -include_lib("test_server/include/test_server.hrl"). @@ -50,7 +52,8 @@ groups() -> [{p,test_lib:parallel(), [dehydrated_itracer,nested_tries,seq_in_guard,make_effect_seq, eval_is_boolean,unsafe_case,nomatch_shadow,reversed_annos, - map_core_test,eval_case,bad_boolean_guard + map_core_test,eval_case,bad_boolean_guard, + bs_shadowed_size_var ]}]. @@ -78,6 +81,8 @@ end_per_group(_GroupName, Config) -> ?comp(map_core_test). ?comp(eval_case). ?comp(bad_boolean_guard). +?comp(bs_shadowed_size_var). + try_it(Mod, Conf) -> Src = filename:join(?config(data_dir, Conf), atom_to_list(Mod)), @@ -87,4 +92,7 @@ try_it(Mod, Conf) -> compile_and_load(Src, Opts) -> {ok,Mod,Bin} = compile:file(Src, [from_core,report,time,binary|Opts]), {module,Mod} = code:load_binary(Mod, Mod, Bin), - ok = Mod:Mod(). + ok = Mod:Mod(), + _ = code:delete(Mod), + _ = code:purge(Mod), + ok. diff --git a/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core b/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core new file mode 100644 index 0000000000..0ade037e05 --- /dev/null +++ b/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core @@ -0,0 +1,66 @@ +module 'bs_shadowed_size_var' + ['filter_essentials'/1, + 'bs_shadowed_size_var'/0] + attributes [] + +%% bs_shadowed_size_var() -> +%% [42|<<"abcde">>] = Mod:filter_essentials([<<42:32>>|<<5:32,"abcde">>]), +%% ok. + +'bs_shadowed_size_var'/0 = + fun () -> + case <> of + <> when 'true' -> + case apply 'filter_essentials'/1 + ([#{#<0>(8,1,'integer',['unsigned'|['big']]), + #<0>(8,1,'integer',['unsigned'|['big']]), + #<0>(8,1,'integer',['unsigned'|['big']]), + #<42>(8,1,'integer',['unsigned'|['big']])}#|#{#<0>(8,1,'integer',['unsigned'|['big']]), + #<0>(8,1,'integer',['unsigned'|['big']]), + #<0>(8,1,'integer',['unsigned'|['big']]), + #<5>(8,1,'integer',['unsigned'|['big']]), + #<97>(8,1,'integer',['unsigned'|['big']]), + #<98>(8,1,'integer',['unsigned'|['big']]), + #<99>(8,1,'integer',['unsigned'|['big']]), + #<100>(8,1,'integer',['unsigned'|['big']]), + #<101>(8,1,'integer',['unsigned'|['big']])}#]) of + <[42|#{#<97>(8,1,'integer',['unsigned'|['big']]), + #<98>(8,1,'integer',['unsigned'|['big']]), + #<99>(8,1,'integer',['unsigned'|['big']]), + #<100>(8,1,'integer',['unsigned'|['big']]), + #<101>(8,1,'integer',['unsigned'|['big']])}#]> when 'true' -> + 'ok' + ( <_cor0> when 'true' -> + primop 'match_fail' + ({'badmatch',_cor0}) + -| ['compiler_generated'] ) + end + ( <> when 'true' -> + ( primop 'match_fail' + ({'function_clause'}) + -| [{'function_name',{'bs_shadowed_size_var',0}}] ) + -| ['compiler_generated'] ) + end + +%% Reduced code from beam_asm inlined using the old inliner. + +'filter_essentials'/1 = + fun (_cor0) -> + case _cor0 of + <[#{#<Sz>(32,1,'integer',['unsigned','big']) }#|T]> when 'true' -> + let <_cor4> = + case T of + %% Variable 'Sz' repeated here. Should work. + <#{#<Sz>(32,1,'integer',['unsigned','big']), + #<Data>(Sz,8,'binary',['unsigned','big'])}#> when 'true' -> + Data + <_cor5> when 'true' -> + primop 'match_fail' + ({'case_clause',{_cor5}}) + end + in [Sz|_cor4] + <_cor5> when 'true' -> + primop 'match_fail' + ({'function_clause',_cor5}) + end +end diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index bc82eaf5aa..bff9806bdd 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -231,15 +231,17 @@ eq(Config) when is_list(Config) -> %% OTP-7117. nested_call_in_case(Config) when is_list(Config) -> - ?line PrivDir = ?config(priv_dir, Config), - ?line Dir = filename:dirname(code:which(?MODULE)), - ?line Core = filename:join(Dir, "nested_call_in_case"), - ?line Opts = [from_core,{outdir,PrivDir}|test_lib:opt_opts(?MODULE)], - ?line io:format("~p", [Opts]), - ?line {ok,Mod} = c:c(Core, Opts), - ?line yes = Mod:a([1,2,3], 2), - ?line no = Mod:a([1,2,3], 4), - ?line {'EXIT',_} = (catch Mod:a(not_a_list, 42)), + PrivDir = ?config(priv_dir, Config), + Dir = test_lib:get_data_dir(Config), + Core = filename:join(Dir, "nested_call_in_case"), + Opts = [from_core,{outdir,PrivDir}|test_lib:opt_opts(?MODULE)], + io:format("~p", [Opts]), + {ok,Mod} = c:c(Core, Opts), + yes = Mod:a([1,2,3], 2), + no = Mod:a([1,2,3], 4), + {'EXIT',_} = (catch Mod:a(not_a_list, 42)), + _ = code:delete(Mod), + _ = code:purge(Mod), ok. guard_try_catch(_Config) -> @@ -345,7 +347,7 @@ bsm_an_inlined(_, _) -> error. unused_multiple_values_error(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), - Dir = filename:dirname(code:which(?MODULE)), + Dir = test_lib:get_data_dir(Config), Core = filename:join(Dir, "unused_multiple_values_error"), Opts = [no_copt,clint,return,from_core,{outdir,PrivDir} |test_lib:opt_opts(?MODULE)], diff --git a/lib/compiler/test/nested_call_in_case.core b/lib/compiler/test/core_fold_SUITE_data/nested_call_in_case.core index 5c6b6909bd..c46906b2ed 100644 --- a/lib/compiler/test/nested_call_in_case.core +++ b/lib/compiler/test/core_fold_SUITE_data/nested_call_in_case.core @@ -16,6 +16,3 @@ module 'nested_call_in_case' ['a'/2] -| ['compiler_generated'] ) end end - - - diff --git a/lib/compiler/test/unused_multiple_values_error.core b/lib/compiler/test/core_fold_SUITE_data/unused_multiple_values_error.core index e06587c936..e06587c936 100644 --- a/lib/compiler/test/unused_multiple_values_error.core +++ b/lib/compiler/test/core_fold_SUITE_data/unused_multiple_values_error.core diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index 08279d9408..d91ee7ea08 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -1614,6 +1614,8 @@ t_tuple_size(Config) when is_list(Config) -> ?line {ok,Mod,Code} = compile:file(File, [from_asm,binary]), ?line code:load_binary(Mod, File, Code), ?line 14 = Mod:t({1,2,3,4}), + _ = code:delete(Mod), + _ = code:purge(Mod), ok. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index 8870315084..8768e47b65 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -37,6 +37,7 @@ t_map_sort_literals/1, t_map_size/1, t_build_and_match_aliasing/1, + t_is_map/1, %% variables t_build_and_match_variables/1, @@ -84,6 +85,7 @@ all() -> t_map_sort_literals, t_map_size, t_build_and_match_aliasing, + t_is_map, %% variables t_build_and_match_variables, @@ -667,14 +669,25 @@ t_map_size(Config) when is_list(Config) -> false = map_is_size(M#{ "c" => 2}, 2), %% Error cases. - {'EXIT',{badarg,_}} = (catch map_size([])), - {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), - {'EXIT',{badarg,_}} = (catch map_size(1)), + {'EXIT',{{badmap,[]},_}} = (catch map_size([])), + {'EXIT',{{badmap,<<1,2,3>>},_}} = (catch map_size(<<1,2,3>>)), + {'EXIT',{{badmap,1},_}} = (catch map_size(1)), ok. map_is_size(M,N) when map_size(M) =:= N -> true; map_is_size(_,_) -> false. +t_is_map(Config) when is_list(Config) -> + true = is_map(#{}), + true = is_map(#{a=>1}), + false = is_map({a,b}), + false = is_map(x), + if is_map(#{}) -> ok end, + if is_map(#{b=>1}) -> ok end, + if not is_map([1,2,3]) -> ok end, + if not is_map(x) -> ok end, + ok. + % test map updates without matching t_update_literals(Config) when is_list(Config) -> Map = #{x=>1,y=>2,z=>3,q=>4}, @@ -861,9 +874,9 @@ t_update_map_expressions(Config) when is_list(Config) -> #{ "a" := b } = F(), - %% Error cases, FIXME: should be 'badmap'? - {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), - {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }), + %% Error cases. + {'EXIT',{{badmap,<<>>},_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{{badmap,[]},_}} = (catch (id([]))#{ a := 42, b => 2 }), ok. @@ -884,8 +897,14 @@ t_update_assoc(Config) when is_list(Config) -> %% Errors cases. BadMap = id(badmap), - {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), - {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting=>val}), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>val}), + {'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting=>val}), + + %% Evaluation order. + {'EXIT',{blurf,_}} = + (catch BadMap#{whatever=>id(error(blurf))}), + {'EXIT',{blurf,_}} = + (catch BadMap#{id(error(blurf))=>whatever}), ok. t_update_assoc_large(Config) when is_list(Config) -> @@ -952,8 +971,8 @@ t_update_assoc_large(Config) when is_list(Config) -> M2 = M0#{13.0:=wrong,13.0=>new}, %% Errors cases. - BadMap = id(badmap), - {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>M0}), + BadMap = id({no,map}), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>M0}), ok. @@ -978,19 +997,29 @@ t_update_exact(Config) when is_list(Config) -> 1.0 => new_val4 }, %% Errors cases. - {'EXIT',{badarg,_}} = (catch ((id(nil))#{ a := b })), - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), - {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{<<0:257>> := val}), %% limitation + {'EXIT',{{badmap,nil},_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,42},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,42.0},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting:=val}), + {'EXIT',{{badkey,<<0:257>>},_}} = + (catch M0#{<<0:257>> := val}), %limitation %% A workaround for a bug allowed an empty map to be updated. - {'EXIT',{badarg,_}} = (catch (id(#{}))#{a:=1}), - {'EXIT',{badarg,_}} = (catch #{}#{a:=1}), + {'EXIT',{{badkey,a},_}} = (catch (id(#{}))#{a:=1}), + {'EXIT',{{badkey,a},_}} = (catch #{}#{a:=1}), Empty = #{}, - {'EXIT',{badarg,_}} = (catch Empty#{a:=1}), + {'EXIT',{{badkey,a},_}} = (catch Empty#{a:=1}), + + %% Evaluation order. + BadMap = id([no,map]), + {'EXIT',{blurf,_}} = + (catch BadMap#{whatever:=id(error(blurf))}), + {'EXIT',{blurf,_}} = + (catch BadMap#{id(error(blurf)):=whatever}), + {'EXIT',{{badmap,BadMap},_}} = + (catch BadMap#{id(nonexisting):=whatever}), ok. t_update_exact_large(Config) when is_list(Config) -> @@ -1068,10 +1097,10 @@ t_update_exact_large(Config) when is_list(Config) -> M2 = M0#{13.0=>wrong,13.0:=new}, %% Errors cases. - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,42},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,42.0},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), ok. @@ -1653,8 +1682,8 @@ t_update_assoc_variables(Config) when is_list(Config) -> %% Errors cases. BadMap = id(badmap), - {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), - {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting=>val}), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>val}), + {'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting=>val}), ok. t_update_exact_variables(Config) when is_list(Config) -> @@ -1684,13 +1713,14 @@ t_update_exact_variables(Config) when is_list(Config) -> #{ "wat" := 3, 2 := a } = id(#{ "wat" => 1, K2 => 2 }#{ K2 := a, "wat" := 3 }), %% Errors cases. - {'EXIT',{badarg,_}} = (catch ((id(nil))#{ a := b })), - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), - {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{<<0:257>> := val}), %% limitation + {'EXIT',{{badmap,nil},_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,42},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,42.0},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting:=val}), + {'EXIT',{{badkey,<<0:257>>},_}} = + (catch M0#{<<0:257>> := val}), %limitation ok. t_nested_pattern_expressions(Config) when is_list(Config) -> diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index d0b7c71be8..e996a55db6 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -583,7 +583,7 @@ maps(Config) when is_list(Config) -> ok. ">>, [], - {warnings,[{4,sys_core_fold,{eval_failure,badarg}}]}}, + {warnings,[{4,sys_core_fold,{eval_failure,badmap}}]}}, {bad_map_src2, <<" t() -> @@ -601,7 +601,7 @@ maps(Config) when is_list(Config) -> ok. ">>, [], - {warnings,[{3,v3_core,bad_map}]}}, + {warnings,[{3,v3_core,badmap}]}}, {ok_map_literal_key, <<" t() -> diff --git a/lib/compiler/test/z_SUITE.erl b/lib/compiler/test/z_SUITE.erl new file mode 100644 index 0000000000..eff8a1877f --- /dev/null +++ b/lib/compiler/test/z_SUITE.erl @@ -0,0 +1,62 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015. 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(z_SUITE). + +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, + loaded/1]). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + test_lib:recompile(?MODULE), + [loaded]. + +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +loaded(_Config) -> + 0 = do_loaded(code:all_loaded(), 0), + ok. + +do_loaded([{M,_}|Ms], E0) -> + E = try + _ = M:module_info(), + _ = M:module_info(functions), + E0 + catch + C:Error -> + Stk = erlang:get_stacktrace(), + io:format("~p:~p\n~p\n", [C,Error,Stk]), + E0 + 1 + end, + do_loaded(Ms, E); +do_loaded([], E) -> E. diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl index 96f9f91808..cfc2a19ccd 100644 --- a/lib/debugger/src/dbg_ieval.erl +++ b/lib/debugger/src/dbg_ieval.erl @@ -658,16 +658,12 @@ expr({map,Line,Fs0}, Bs0, Ieval) -> expr({map,Line,E0,Fs0}, Bs0, Ieval0) -> Ieval = Ieval0#ieval{line=Line,top=false}, {value,E,Bs1} = expr(E0, Bs0, Ieval), - case E of - #{} -> - {Fs,Bs2} = eval_map_fields(Fs0, Bs0, Ieval), - Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi); - ({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) - end, E, Fs), - {value,Value,merge_bindings(Bs2, Bs1, Ieval)}; - _ -> - exception(error, {badarg,E}, Bs1, Ieval) - end; + {Fs,Bs2} = eval_map_fields(Fs0, Bs0, Ieval), + _ = maps:put(k, v, E), %Validate map. + Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi); + ({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) + end, E, Fs), + {value,Value,merge_bindings(Bs2, Bs1, Ieval)}; %% A block of statements expr({block,Line,Es},Bs,Ieval) -> seq(Es, Bs, Ieval#ieval{line=Line}); diff --git a/lib/debugger/test/int_eval_SUITE.erl b/lib/debugger/test/int_eval_SUITE.erl index 9d7ef238e3..cda5c97ec4 100644 --- a/lib/debugger/test/int_eval_SUITE.erl +++ b/lib/debugger/test/int_eval_SUITE.erl @@ -293,7 +293,7 @@ stacktrace(Config) when is_list(Config) -> maps(Config) when is_list(Config) -> Fun = fun () -> ?IM:empty_map_update([camembert]) end, - {'EXIT',{{badarg,[camembert]},_}} = spawn_eval(Fun), + {'EXIT',{{badmap,[camembert]},_}} = spawn_eval(Fun), [#{hello := 0, price := 0}] = spawn_eval(fun () -> ?IM:update_in_fun() end), ok. diff --git a/lib/debugger/test/map_SUITE.erl b/lib/debugger/test/map_SUITE.erl index ac2e16f00a..457863d982 100644 --- a/lib/debugger/test/map_SUITE.erl +++ b/lib/debugger/test/map_SUITE.erl @@ -671,9 +671,10 @@ t_map_size(Config) when is_list(Config) -> false = map_is_size(M#{ "c" => 2}, 2), %% Error cases. - {'EXIT',{badarg,_}} = (catch map_size([])), - {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), - {'EXIT',{badarg,_}} = (catch map_size(1)), + do_badmap(fun(T) -> + {'EXIT',{{badmap,T},_}} = + (catch map_size(T)) + end), ok. map_is_size(M,N) when map_size(M) =:= N -> true; @@ -849,9 +850,9 @@ t_update_map_expressions(Config) when is_list(Config) -> #{ a :=42, b:=42, c:=42 } = (maps:from_list([{a,1},{b,2},{c,3}]))#{ a := 42, b := 42, c := 42 }, #{ "a" :=1, "b":=42, "c":=42 } = (maps:from_list([{"a",1},{"b",2}]))#{ "b" := 42, "c" => 42 }, - %% Error cases, FIXME: should be 'badmap'? - {'EXIT',{{badarg,<<>>},_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), - {'EXIT',{{badarg,[]},_}} = (catch (id([]))#{ a := 42, b => 2 }), + %% Error cases. + {'EXIT',{{badmap,<<>>},_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{{badmap,[]},_}} = (catch (id([]))#{ a := 42, b => 2 }), ok. @@ -867,8 +868,8 @@ t_update_assoc(Config) when is_list(Config) -> M2 = M0#{3.0:=wrong,3.0=>new}, %% Errors cases. - BadMap = id(badmap), - {'EXIT',{{badarg,BadMap},_}} = (catch BadMap#{nonexisting=>val}), + BadMap = id(not_a_good_map), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>val}), ok. @@ -936,8 +937,8 @@ t_update_assoc_large(Config) when is_list(Config) -> M2 = M0#{13.0:=wrong,13.0=>new}, %% Errors cases. - BadMap = id(badmap), - {'EXIT',{{badarg,BadMap},_}} = (catch BadMap#{nonexisting=>M0}), + BadMap = id(a_bad_map), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>M0}), ok. @@ -954,10 +955,25 @@ t_update_exact(Config) when is_list(Config) -> %% M2 = M0#{3=>wrong,3.0:=new}, %% FIXME %% Errors cases. - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + do_badmap(fun(T) -> + {'EXIT',{{badmap,T},_}} = + (catch T#{nonexisting=>val}) + end), + Empty = id(#{}), + {'EXIT',{{badkey,nonexisting},_}} = (catch Empty#{nonexisting:=val}), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + + %% Evaluation order. + BadMap = id([no,map]), + {'EXIT',{blurf,_}} = + (catch BadMap#{whatever:=id(error(blurf))}), + {'EXIT',{blurf,_}} = + (catch BadMap#{id(error(blurf)):=whatever}), + {'EXIT',{{badmap,BadMap},_}} = + (catch BadMap#{nonexisting:=whatever}), ok. t_update_exact_large(Config) when is_list(Config) -> @@ -1035,10 +1051,10 @@ t_update_exact_large(Config) when is_list(Config) -> M2 = M0#{13.0=>wrong,13.0:=new}, %% Errors cases. - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), ok. @@ -1443,11 +1459,11 @@ t_bif_map_get(Config) when is_list(Config) -> "v4" = maps:get(<<"k2">>, M#{ <<"k2">> => "v4" }), %% error case - {'EXIT',{badarg, [{maps,get,_,_}|_]}} = (catch maps:get(a,[])), - {'EXIT',{badarg, [{maps,get,_,_}|_]}} = (catch maps:get(a,<<>>)), - {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get({1,1}, #{{1,1.0} => "tuple"})), - {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get(a,#{})), - {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get(a,#{ b=>1, c=>2})), + {'EXIT',{{badmap,[]},[{maps,get,_,_}|_]}} = (catch maps:get(a, [])), + {'EXIT',{{badmap,<<>>},[{maps,get,_,_}|_]}} = (catch maps:get(a, <<>>)), + {'EXIT',{{badkey,{1,1}},[{maps,get,_,_}|_]}} = (catch maps:get({1,1}, #{{1,1.0} => "tuple"})), + {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} = (catch maps:get(a, #{})), + {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} = (catch maps:get(a, #{ b=>1, c=>2})), ok. t_bif_map_find(Config) when is_list(Config) -> @@ -1471,8 +1487,10 @@ t_bif_map_find(Config) when is_list(Config) -> error = maps:find({1.0,1}, #{ a=>a, {1,1.0} => "tuple hi"}), % reverse types in tuple key - {'EXIT',{badarg,[{maps,find,_,_}|_]}} = (catch maps:find(a,id([]))), - {'EXIT',{badarg,[{maps,find,_,_}|_]}} = (catch maps:find(a,id(<<>>))), + {'EXIT',{{badmap,[]},[{maps,find,_,_}|_]}} = + (catch maps:find(a, id([]))), + {'EXIT',{{badmap,<<>>},[{maps,find,_,_}|_]}} = + (catch maps:find(a, id(<<>>))), ok. @@ -1497,8 +1515,8 @@ t_bif_map_is_key(Config) when is_list(Config) -> false = maps:is_key(1.0, maps:put(1, "number", M1)), %% error case - {'EXIT',{badarg,[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a,id([]))), - {'EXIT',{badarg,[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a,id(<<>>))), + {'EXIT',{{badmap,[]},[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a, id([]))), + {'EXIT',{{badmap,<<>>},[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a, id(<<>>))), ok. t_bif_map_keys(Config) when is_list(Config) -> @@ -1511,11 +1529,12 @@ t_bif_map_keys(Config) when is_list(Config) -> [4,int,"hi",<<"key">>] = lists:sort(maps:keys(M1)), %% error case - {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(1 bsl 65 + 3)), - {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(154)), - {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(atom)), - {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys([])), - {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(<<>>)), + BigNum = 1 bsl 65 + 3, + {'EXIT',{{badmap,BigNum},[{maps,keys,_,_}|_]}} = (catch maps:keys(BigNum)), + {'EXIT',{{badmap,154},[{maps,keys,_,_}|_]}} = (catch maps:keys(154)), + {'EXIT',{{badmap,atom},[{maps,keys,_,_}|_]}} = (catch maps:keys(atom)), + {'EXIT',{{badmap,[]},[{maps,keys,_,_}|_]}} = (catch maps:keys([])), + {'EXIT',{{badmap,<<>>},[{maps,keys,_,_}|_]}} = (catch maps:keys(<<>>)), ok. t_bif_map_new(Config) when is_list(Config) -> @@ -1544,9 +1563,10 @@ t_bif_map_merge(Config) when is_list(Config) -> {1,2} := "tuple", "hi" := "hello again", <<"key">> := <<"value">>} = maps:merge(M0,M1), %% error case - {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge((1 bsl 65 + 3), <<>>)), - {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge(<<>>, id(#{ a => 1}))), - {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge(id(#{ a => 2}), <<>> )), + BigNum = 1 bsl 65 + 3, + {'EXIT',{{badmap,BigNum},[{maps,merge,_,_}|_]}} = (catch maps:merge(BigNum, <<>>)), + {'EXIT',{{badmap,<<>>},[{maps,merge,_,_}|_]}} = (catch maps:merge(<<>>, id(#{ a => 1}))), + {'EXIT',{{badmap,<<>>},[{maps,merge,_,_}|_]}} = (catch maps:merge(id(#{ a => 2}), <<>> )), ok. @@ -1585,11 +1605,12 @@ t_bif_map_put(Config) when is_list(Config) -> true = is_members([number,wat,3,"hello",<<"other value">>],maps:values(M6)), %% error case - {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,1 bsl 65 + 3)), - {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,154)), - {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,atom)), - {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,[])), - {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,<<>>)), + BigNum = 1 bsl 65 + 3, + {'EXIT',{{badmap,BigNum},[{maps,put,_,_}|_]}} = (catch maps:put(1, a, BigNum)), + {'EXIT',{{badmap,154},[{maps,put,_,_}|_]}} = (catch maps:put(1, a, 154)), + {'EXIT',{{badmap,atom},[{maps,put,_,_}|_]}} = (catch maps:put(1, a, atom)), + {'EXIT',{{badmap,[]},[{maps,put,_,_}|_]}} = (catch maps:put(1, a, [])), + {'EXIT',{{badmap,<<>>},[{maps,put,_,_}|_]}} = (catch maps:put(1, a, <<>>)), ok. is_members(Ks,Ls) when length(Ks) =/= length(Ls) -> false; @@ -1621,9 +1642,10 @@ t_bif_map_update(Config) when is_list(Config) -> 4 := number, 18446744073709551629 := wazzup} = maps:update(18446744073709551629, wazzup, M0), %% error case - {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(1,none,{})), - {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(1,none,<<"value">>)), - {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(5,none,M0)), + {'EXIT',{{badmap,{}},[{maps,update,_,_}|_]}} = (catch maps:update(1, none, {})), + {'EXIT',{{badmap,<<"value">>},[{maps,update,_,_}|_]}} = + (catch maps:update(1, none, <<"value">>)), + {'EXIT',{{badkey,5},[{maps,update,_,_}|_]}} = (catch maps:update(5, none, M0)), ok. @@ -1659,12 +1681,13 @@ t_bif_map_remove(Config) when is_list(Config) -> #{ "hi" := "hello", int := 3, 4 := number} = maps:remove(18446744073709551629,maps:remove(<<"key">>,M0)), %% error case - {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,1 bsl 65 + 3)), - {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(1,154)), - {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,atom)), - {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(1,[])), - {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,<<>>)), - ok. + BigNum = 1 bsl 65 + 3, + {'EXIT',{{badmap,BigNum},[{maps,remove,_,_}|_]}} = (catch maps:remove(a, BigNum)), + {'EXIT',{{badmap,154},[{maps,remove,_,_}|_]}} = (catch maps:remove(1, 154)), + {'EXIT',{{badmap,atom},[{maps,remove,_,_}|_]}} = (catch maps:remove(a, atom)), + {'EXIT',{{badmap,[]},[{maps,remove,_,_}|_]}} = (catch maps:remove(1, [])), + {'EXIT',{{badmap,<<>>},[{maps,remove,_,_}|_]}} = (catch maps:remove(a, <<>>)), + ok. t_bif_map_values(Config) when is_list(Config) -> @@ -1680,10 +1703,11 @@ t_bif_map_values(Config) when is_list(Config) -> true = is_members([number,3,"hello",<<"value">>],maps:values(M1)), %% error case - {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(1 bsl 65 + 3)), - {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(atom)), - {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values([])), - {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(<<>>)), + BigNum = 1 bsl 65 + 3, + {'EXIT',{{badmap,BigNum},[{maps,values,_,_}|_]}} = (catch maps:values(BigNum)), + {'EXIT',{{badmap,atom},[{maps,values,_,_}|_]}} = (catch maps:values(atom)), + {'EXIT',{{badmap,[]},[{maps,values,_,_}|_]}} = (catch maps:values([])), + {'EXIT',{{badmap,<<>>},[{maps,values,_,_}|_]}} = (catch maps:values(<<>>)), ok. @@ -1854,8 +1878,8 @@ t_bif_map_to_list(Config) when is_list(Config) -> <<"hi">>=>v6,3=>v7,"hi"=>v8,hi=>v9,{hi,3}=>v10})), %% error cases - {'EXIT', {badarg,_}} = (catch maps:to_list(id(a))), - {'EXIT', {badarg,_}} = (catch maps:to_list(id(42))), + {'EXIT', {{badmap,a},_}} = (catch maps:to_list(id(a))), + {'EXIT', {{badmap,42},_}} = (catch maps:to_list(id(42))), ok. @@ -2071,9 +2095,9 @@ t_update_assoc_variables(Config) when is_list(Config) -> #{ <<0:258>> := val } = id(M0#{<<0:258>> => val}), %% binary limitation %% Errors cases. - BadMap = id(badmap), - {'EXIT',{{badarg,_},_}} = (catch BadMap#{nonexisting=>val}), - {'EXIT',{{badarg,_},_}} = (catch <<>>#{nonexisting=>val}), + BadMap = id(a_bad_map), + {'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>val}), + {'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting=>val}), ok. t_update_exact_variables(Config) when is_list(Config) -> @@ -2101,14 +2125,14 @@ t_update_exact_variables(Config) when is_list(Config) -> 1.0 => new_val4 }, %% Errors cases. - {'EXIT',{{badarg,_},_}} = (catch ((id(nil))#{ a := b })), - {'EXIT',{{badarg,_},_}} = (catch <<>>#{nonexisting:=val}), - - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), - {'EXIT',{badarg,_}} = (catch M0#{<<0:257>> := val}), %% limitation + {'EXIT',{{badmap,_},_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{{badmap,_},_}} = (catch <<>>#{nonexisting:=val}), + + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badkey,_},_}} = (catch M0#{<<0:257>> := val}), %% limitation ok. t_nested_pattern_expressions(Config) when is_list(Config) -> @@ -2359,6 +2383,11 @@ t_build_and_match_structure(Config) when is_list(Config) -> ok. +do_badmap(Test) -> + Terms = [Test,fun erlang:abs/1,make_ref(),self(),0.0/id(-1), + <<1:1>>,<<>>,<<1,2,3>>, + [],{a,b,c},[a,b],atom,10.0,42,(1 bsl 65) + 3], + [Test(T) || T <- Terms]. %% Use this function to avoid compile-time evaluation of an expression. id(I) -> I. diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml index b52c1edebf..2a8bf6edcc 100644 --- a/lib/dialyzer/doc/src/dialyzer.xml +++ b/lib/dialyzer/doc/src/dialyzer.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2006</year><year>2014</year> + <year>2006</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -231,6 +231,8 @@ <tag><c><![CDATA[-Wno_behaviours]]></c></tag> <item>Suppress warnings about behaviour callbacks which drift from the published recommended interfaces.</item> + <tag><c><![CDATA[-Wno_missing_calls]]></c></tag> + <item>Suppress warnings about calls to missing functions.</item> <tag><c><![CDATA[-Wno_undefined_callbacks]]></c></tag> <item>Suppress warnings about behaviours that have no <c>-callback</c> attributes for their callbacks.</item> @@ -246,9 +248,16 @@ analysis that finds data races performs intra-procedural data flow analysis and can sometimes explode in time. Enable it at your own risk. </item> - <tag><c><![CDATA[-Wunderspecs]]></c>***</tag> +i <tag><c><![CDATA[-Wunderspecs]]></c>***</tag> <item>Warn about underspecified functions (the -spec is strictly more allowing than the success typing).</item> + <tag><c><![CDATA[-Wunknown]]></c>***</tag> + <item>Let warnings about unknown functions and types affect the + exit status of the command line version. The default is to ignore + warnings about unknown functions and types when setting the exit + status. When using the Dialyzer from Erlang, warnings about unknown + functions and types are returned; the default is not to return + these warnings.</item> </taglist> <p>The following options are also available but their use is not recommended: (they are mostly for Dialyzer developers and internal @@ -382,7 +391,8 @@ WarnOpts :: no_return | race_conditions | overspecs | underspecs - | specdiffs</code> + | specdiffs + | unknown</code> </desc> </func> <func> @@ -416,6 +426,7 @@ Tag :: 'warn_behaviour' | 'warn_return_only_exit' | 'warn_umatched_return' | 'warn_undefined_callbacks' + | 'warn_unknown' Id = {File :: string(), Line :: integer()} Msg = msg() -- Undefined</code> </desc> diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 5ff7ad9c6f..dbfe680345 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -2,7 +2,7 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2014. All Rights Reserved. +%% Copyright Ericsson AB 2006-2015. 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 @@ -586,7 +586,8 @@ send_codeserver_plt(Parent, CServer, Plt ) -> send_bad_calls(Parent, BadCalls, CodeServer) -> FormatedBadCalls = format_bad_calls(BadCalls, CodeServer, []), - send_warnings(Parent, FormatedBadCalls). + Warnings = filter_warnings(FormatedBadCalls, CodeServer), + send_warnings(Parent, Warnings). send_mod_deps(Parent, ModuleDeps) -> Parent ! {self(), mod_deps, ModuleDeps}, diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index fe4ec41359..4386a8d52a 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -656,15 +656,15 @@ return_value(State = #cl_state{erlang_mode = ErlangMode, mod_deps = ModDeps, output_plt = OutputPlt, plt_info = PltInfo, - stored_warnings = StoredWarnings, - legal_warnings = LegalWarnings}, + stored_warnings = StoredWarnings}, Plt) -> case OutputPlt =:= none of true -> ok; false -> dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo) end, + UnknownWarnings = unknown_warnings(State), RetValue = - case StoredWarnings =:= [] of + case StoredWarnings =:= [] andalso UnknownWarnings =:= [] of true -> ?RET_NOTHING_SUSPICIOUS; false -> ?RET_DISCREPANCIES end, @@ -677,22 +677,22 @@ return_value(State = #cl_state{erlang_mode = ErlangMode, maybe_close_output_file(State), {RetValue, []}; true -> - Unknown = - case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of - true -> - unknown_functions(State) ++ - unknown_types(State) ++ - unknown_behaviours(State); - false -> [] - end, - WarningInfo = {_Filename = "", _Line = 0, _MorMFA = ''}, - UnknownWarnings = - [{?WARN_UNKNOWN, WarningInfo, W} || W <- Unknown], AllWarnings = UnknownWarnings ++ process_warnings(StoredWarnings), {RetValue, set_warning_id(AllWarnings)} end. +unknown_warnings(State = #cl_state{legal_warnings = LegalWarnings}) -> + Unknown = case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of + true -> + unknown_functions(State) ++ + unknown_types(State) ++ + unknown_behaviours(State); + false -> [] + end, + WarningInfo = {_Filename = "", _Line = 0, _MorMFA = ''}, + [{?WARN_UNKNOWN, WarningInfo, W} || W <- Unknown]. + unknown_functions(#cl_state{external_calls = Calls}) -> [{unknown_function, MFA} || MFA <- Calls]. @@ -706,10 +706,8 @@ print_ext_calls(#cl_state{report_mode = quiet}) -> print_ext_calls(#cl_state{output = Output, external_calls = Calls, stored_warnings = Warnings, - output_format = Format, - legal_warnings = LegalWarnings}) -> - case not ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) - orelse Calls =:= [] of + output_format = Format}) -> + case Calls =:= [] of true -> ok; false -> case Warnings =:= [] of @@ -741,10 +739,8 @@ print_ext_types(#cl_state{output = Output, external_calls = Calls, external_types = Types, stored_warnings = Warnings, - output_format = Format, - legal_warnings = LegalWarnings}) -> - case not ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) - orelse Types =:= [] of + output_format = Format}) -> + case Types =:= [] of true -> ok; false -> case Warnings =:= [] andalso Calls =:= [] of diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index 04ce0e8bc3..21fc424a1b 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2014. All Rights Reserved. +%% Copyright Ericsson AB 2006-2015. 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 @@ -509,6 +509,8 @@ warning_options_msg() -> -Wno_behaviours Suppress warnings about behaviour callbacks which drift from the published recommended interfaces. + -Wno_missing_calls + Suppress warnings about calls to missing functions. -Wno_undefined_callbacks Suppress warnings about behaviours that have no -callback attributes for their callbacks. @@ -522,6 +524,13 @@ warning_options_msg() -> -Wunderspecs *** Warn about underspecified functions (those whose -spec is strictly more allowing than the success typing). + -Wunknown *** + Let warnings about unknown functions and types affect the + exit status of the command line version. The default is to ignore + warnings about unknown functions and types when setting the exit + status. When using the Dialyzer from Erlang, warnings about unknown + functions and types are returned; the default is not to return + such warnings. The following options are also available but their use is not recommended: (they are mostly for Dialyzer developers and internal debugging) diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 20971f1407..81907f7995 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2014. All Rights Reserved. +%% Copyright Ericsson AB 2006-2015. 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 @@ -46,13 +46,11 @@ build(Opts) -> ?WARN_CALLGRAPH, ?WARN_FAILING_CALL, ?WARN_BIN_CONSTRUCTION, - ?WARN_CALLGRAPH, ?WARN_CONTRACT_RANGE, ?WARN_CONTRACT_TYPES, ?WARN_CONTRACT_SYNTAX, ?WARN_BEHAVIOUR, - ?WARN_UNDEFINED_CALLBACK, - ?WARN_UNKNOWN], + ?WARN_UNDEFINED_CALLBACK], DefaultWarns1 = ordsets:from_list(DefaultWarns), InitPlt = dialyzer_plt:get_default_plt(), DefaultOpts = #options{}, @@ -302,6 +300,8 @@ build_warnings([Opt|Opts], Warnings) -> ordsets:add_element(?WARN_RETURN_ONLY_EXIT, Warnings); race_conditions -> ordsets:add_element(?WARN_RACE_CONDITION, Warnings); + no_missing_calls -> + ordsets:del_element(?WARN_CALLGRAPH, Warnings); specdiffs -> S = ordsets:from_list([?WARN_CONTRACT_SUBTYPE, ?WARN_CONTRACT_SUPERTYPE, @@ -311,8 +311,8 @@ build_warnings([Opt|Opts], Warnings) -> ordsets:add_element(?WARN_CONTRACT_SUBTYPE, Warnings); underspecs -> ordsets:add_element(?WARN_CONTRACT_SUPERTYPE, Warnings); - no_unknown -> - ordsets:del_element(?WARN_UNKNOWN, Warnings); + unknown -> + ordsets:add_element(?WARN_UNKNOWN, Warnings); OtherAtom -> bad_option("Unknown dialyzer warning option", OtherAtom) end, diff --git a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options index 44a65f6e90..3ff26b87db 100644 --- a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options @@ -1 +1 @@ -{dialyzer_options, [{warnings, [no_unused, no_return, no_unknown]}]}. +{dialyzer_options, [{warnings, [no_unused, no_return]}]}. diff --git a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options index 65d233ac0d..c612e77d3e 100644 --- a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options @@ -1,2 +1,2 @@ -{dialyzer_options, [{include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists, no_unknown]}]}. +{dialyzer_options, [{include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists]}]}. {time_limit, 30}. diff --git a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options index ba0e6b1ad7..e00e23bb66 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options @@ -1,2 +1,2 @@ -{dialyzer_options, [{defines, [{vsn, 42}]}, {warnings, [no_unknown]}]}. +{dialyzer_options, [{defines, [{vsn, 42}]}]}. {time_limit, 20}. diff --git a/lib/dialyzer/test/race_SUITE_data/dialyzer_options b/lib/dialyzer/test/race_SUITE_data/dialyzer_options index 6992fc6c40..44e1720715 100644 --- a/lib/dialyzer/test/race_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/race_SUITE_data/dialyzer_options @@ -1 +1 @@ -{dialyzer_options, [{warnings, [race_conditions, no_unknown]}]}. +{dialyzer_options, [{warnings, [race_conditions]}]}. diff --git a/lib/dialyzer/test/small_SUITE_data/dialyzer_options b/lib/dialyzer/test/small_SUITE_data/dialyzer_options index 0d91699e4d..50991c9bc5 100644 --- a/lib/dialyzer/test/small_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/small_SUITE_data/dialyzer_options @@ -1 +1 @@ -{dialyzer_options, [{warnings, [no_unknown]}]}. +{dialyzer_options, []}. diff --git a/lib/dialyzer/test/small_SUITE_data/src/suppression3.erl b/lib/dialyzer/test/small_SUITE_data/src/suppression3.erl new file mode 100644 index 0000000000..4a745cffc2 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/suppression3.erl @@ -0,0 +1,17 @@ +-module(suppression3). + +-export([a/1, b/1]). + +-dialyzer({nowarn_function, a/1}). + +-spec a(_) -> integer(). + +a(A) -> + ?MODULE:missing(A). + +-dialyzer({no_missing_calls, b/1}). + +-spec b(_) -> integer(). + +b(A) -> + ?MODULE:missing(A). diff --git a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options index 6843119b9d..f7197ac30f 100644 --- a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options @@ -1 +1 @@ -{dialyzer_options, [{warnings, [underspecs, no_unknown]}]}. +{dialyzer_options, [{warnings, [underspecs]}]}. diff --git a/lib/dialyzer/test/user_SUITE_data/dialyzer_options b/lib/dialyzer/test/user_SUITE_data/dialyzer_options index d20ecd389f..513ed7752b 100644 --- a/lib/dialyzer/test/user_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/user_SUITE_data/dialyzer_options @@ -1,2 +1,2 @@ -{dialyzer_options, [{warnings, [no_unknown]}]}. +{dialyzer_options, []}. {time_limit, 3}.
\ No newline at end of file diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 09dffe1280..798212d5f9 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -2636,15 +2636,19 @@ inf_collect(_T1, [], _Opaques, OpL) -> combine(S, T1, T2) -> #opaque{mod = Mod1, name = Name1, args = Args1} = T1, #opaque{mod = Mod2, name = Name2, args = Args2} = T2, + Comb1 = comb(Mod1, Name1, Args1, S, T1), case is_same_type_name({Mod1, Name1, Args1}, {Mod2, Name2, Args2}) of - true -> [comb(Mod1, Name1, Args1, S, T1)]; - false -> [comb(Mod1, Name1, Args1, S, T1), comb(Mod2, Name2, Args2, S, T2)] + true -> Comb1; + false -> Comb1 ++ comb(Mod2, Name2, Args2, S, T2) end. comb(Mod, Name, Args, S, T) -> case is_same_name(Mod, Name, Args, S) of - true -> S; - false -> T#opaque{struct = S} + true -> + ?opaque(Set) = S, + Set; + false -> + [T#opaque{struct = S}] end. is_same_name(Mod1, Name1, Args1, diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl index 4691662f9f..3e099fcc25 100644 --- a/lib/hipe/icode/hipe_beam_to_icode.erl +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -1584,11 +1584,7 @@ gen_put_map_instrs(exists, Op, TempMapVar, Dst, FailLbl, Pairs, Env) -> end, {[IsMapCode, TrueLabel, PutInstructions, ReturnLbl], Env1}; gen_put_map_instrs(new, Op, TempMapVar, Dst, new, Pairs, Env) -> - TrueLabel = mk_label(new), FailLbl = mk_label(new), - IsMapCode = hipe_icode:mk_type([TempMapVar], map, - hipe_icode:label_name(TrueLabel), - hipe_icode:label_name(FailLbl)), DstMapVar = mk_var(Dst), {ReturnLbl, PutInstructions, Env1} = case Op of @@ -1596,10 +1592,10 @@ gen_put_map_instrs(new, Op, TempMapVar, Dst, new, Pairs, Env) -> trans_put_map_assoc(TempMapVar, DstMapVar, Pairs, Env, []); exact -> trans_put_map_exact(TempMapVar, DstMapVar, - hipe_icode:label_name(FailLbl), Pairs, Env, []) + none, Pairs, Env, []) end, Fail = hipe_icode:mk_fail([hipe_icode:mk_const(badarg)], error), - {[IsMapCode, TrueLabel, PutInstructions, FailLbl, Fail, ReturnLbl], Env1}. + {[PutInstructions, FailLbl, Fail, ReturnLbl], Env1}. %%----------------------------------------------------------------------- %% This function generates the instructions needed to insert several @@ -1629,6 +1625,13 @@ trans_put_map_exact(MapVar, DestMapVar, _FLbl, [], Env, Acc) -> ReturnLbl = mk_label(new), GotoReturn = hipe_icode:mk_goto(hipe_icode:label_name(ReturnLbl)), {ReturnLbl, lists:reverse([GotoReturn, MoveToReturnVar | Acc]), Env}; +trans_put_map_exact(MapVar, DestMapVar, none, [Key, Value | Rest], Env, Acc) -> + {MoveKey, KeyVar, Env1} = mk_move_and_var(Key, Env), + {MoveVal, ValVar, Env2} = mk_move_and_var(Value, Env1), + BifCallPut = hipe_icode:mk_call([MapVar], maps, update, + [KeyVar, ValVar, MapVar], remote), + Acc1 = [BifCallPut, MoveVal, MoveKey | Acc], + trans_put_map_exact(MapVar, DestMapVar, none, Rest, Env2, Acc1); trans_put_map_exact(MapVar, DestMapVar, FLbl, [Key, Value | Rest], Env, Acc) -> SuccLbl = mk_label(new), {MoveKey, KeyVar, Env1} = mk_move_and_var(Key, Env), diff --git a/lib/hipe/test/maps_SUITE_data/maps_map_size.erl b/lib/hipe/test/maps_SUITE_data/maps_map_size.erl index 25c8e5d4c7..3cd2d90dfb 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_map_size.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_map_size.erl @@ -17,9 +17,9 @@ test() -> false = map_is_size(M#{ "c" => 2}, 2), %% Error cases. - {'EXIT',{badarg,_}} = (catch map_size([])), - {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), - {'EXIT',{badarg,_}} = (catch map_size(1)), + {'EXIT',{{badmap,[]},_}} = (catch map_size([])), + {'EXIT',{{badmap,<<1,2,3>>},_}} = (catch map_size(<<1,2,3>>)), + {'EXIT',{{badmap,1},_}} = (catch map_size(1)), ok. map_is_size(M,N) when map_size(M) =:= N -> true; diff --git a/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl b/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl index 72ac9ce078..2fe4f204d1 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_put_map_assoc.erl @@ -8,7 +8,7 @@ test() -> true = assoc_guard(#{}), false = assoc_guard(not_a_map), #{a := true} = assoc_update(#{}), - {'EXIT', {badarg, [{?MODULE, assoc_update, 1, _}|_]}} + {'EXIT', {{badmap, not_a_map}, [{?MODULE, assoc_update, 1, _}|_]}} = (catch assoc_update(not_a_map)), ok = assoc_guard_clause(#{}), {'EXIT', {function_clause, [{?MODULE, assoc_guard_clause, _, _}|_]}} diff --git a/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl b/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl index 1cfcd80180..3c85289a36 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_put_map_exact.erl @@ -9,9 +9,9 @@ test() -> false = exact_guard(not_a_map), true = exact_guard(#{a => false}), #{a := true} = exact_update(#{a => false}), - {'EXIT', {badarg, [{?MODULE, exact_update, 1, _}|_]}} + {'EXIT', {{badmap, not_a_map}, [{?MODULE, exact_update, 1, _}|_]}} = (catch exact_update(not_a_map)), - {'EXIT', {badarg, [{?MODULE, exact_update, 1, _}|_]}} + {'EXIT', {{badkey, a}, [{?MODULE, exact_update, 1, _}|_]}} = (catch exact_update(#{})), ok = exact_guard_clause(#{a => yes}), {'EXIT', {function_clause, [{?MODULE, exact_guard_clause, _, _}|_]}} diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl b/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl index cc7c1353de..99228a1927 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_assoc.erl @@ -14,7 +14,7 @@ test() -> %% Errors cases. BadMap = id(badmap), - {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), + {'EXIT',{{badmap,badmap},_}} = (catch BadMap#{nonexisting=>val}), ok. diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl b/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl index 6e5acb3283..1c38820a7c 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_exact.erl @@ -21,11 +21,11 @@ test() -> 1.0 => new_val4 }, %% Errors cases. - {'EXIT',{badarg,_}} = (catch ((id(nil))#{ a := b })), - {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), - {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), - {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), - {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + {'EXIT',{{badmap,nil},_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{{badkey,_},_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{{badkey,_},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), ok. %% Use this function to avoid compile-time evaluation of an expression. diff --git a/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl b/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl index 181e3f18f7..213fc33d97 100644 --- a/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl +++ b/lib/hipe/test/maps_SUITE_data/maps_update_map_expressions.erl @@ -23,9 +23,9 @@ test() -> #{ "a" := b } = F(), - %% Error cases, FIXME: should be 'badmap'? - {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), - {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }), + %% Error cases. + {'EXIT',{{badmap,<<>>},_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{{badmap,[]},_}} = (catch (id([]))#{ a := 42, b => 2 }), ok. %% Use this function to avoid compile-time evaluation of an expression. diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 20c8a6b1b1..e40660ab39 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -315,7 +315,7 @@ text/plain asc txt </item> <marker id="prop_server_tokens"></marker> - <tag>{server_tokens, prod|major|minor|minimal|os|full|{private, string()}}</tag> + <tag>{server_tokens, none|prod|major|minor|minimal|os|full|{private, string()}}</tag> <item> <p>ServerTokens defines how the value of the server header should look. </p> @@ -323,6 +323,7 @@ text/plain asc txt here is what the server header string could look like for the different values of server-tokens: </p> <pre> +none "" % A Server: header will not be generated prod "inets" major "inets/5" minor "inets/5.8" diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 2c3ee79f31..12bbc2b736 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,7 +32,28 @@ <file>notes.xml</file> </header> - <section><title>Inets 5.10.6</title> + <section><title>Inets 5.10.7</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + New value in <c>server_tokens</c> config for limiting + banner grabbing attempts. </p> + <p> + By setting <c>{server_tokens, none}</c> in + <c>ServiceConfig</c> for <c>inets:start(httpd, + ServiceConfig)</c>, the "Server:" header will not be set + in messages from the server.</p> + <p> + Own Id: OTP-12661 Aux Id: seq12840 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 5.10.6</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 78dda794db..dbdc1be272 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -219,14 +219,14 @@ load("ServerName " ++ ServerName, []) -> load("ServerTokens " ++ ServerTokens, []) -> %% These are the valid *plain* server tokens: - %% sprod, major, minor, minimum, os, full + %% none, prod, major, minor, minimum, os, full %% It can also be a "private" server token: private:<any string> case string:tokens(ServerTokens, [$:]) of ["private", Private] -> {ok,[], {server_tokens, clean(Private)}}; [TokStr] -> Tok = list_to_atom(clean(TokStr)), - case lists:member(Tok, [prod, major, minor, minimum, os, full]) of + case lists:member(Tok, [none, prod, major, minor, minimum, os, full]) of true -> {ok,[], {server_tokens, Tok}}; false -> @@ -850,6 +850,8 @@ server(full = _ServerTokens) -> OS = os_info(full), lists:flatten( io_lib:format("~s ~s OTP/~s", [?SERVER_SOFTWARE, OS, OTPRelease])); +server(none = _ServerTokens) -> + ""; server({private, Server} = _ServerTokens) when is_list(Server) -> %% The user provide its own Server; @@ -1299,7 +1301,7 @@ ssl_ca_certificate_file(ConfigDB) -> end. plain_server_tokens() -> - [prod, major, minor, minimum, os, full]. + [none, prod, major, minor, minimum, os, full]. error_report(Where,M,F,Error) -> error_logger:error_report([{?MODULE, Where}, diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 0895729d05..2fa91d47a0 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -287,8 +287,11 @@ create_header(ConfigDb, KeyValueTupleHeaders) -> ContentType = "text/html", Server = server(ConfigDb), NewHeaders = add_default_headers([{"date", Date}, - {"content-type", ContentType}, - {"server", Server}], + {"content-type", ContentType} + | if Server=="" -> []; + true -> [{"server", Server}] + end + ], KeyValueTupleHeaders), lists:map(fun fix_header/1, NewHeaders). diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index e5b63a6446..e9ecb2632a 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,6 +18,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.10.6 +INETS_VSN = 5.10.7 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/doc/src/jinterface_users_guide.xml b/lib/jinterface/doc/src/jinterface_users_guide.xml index 5dfe5c0c6d..238f90ce38 100644 --- a/lib/jinterface/doc/src/jinterface_users_guide.xml +++ b/lib/jinterface/doc/src/jinterface_users_guide.xml @@ -223,6 +223,14 @@ OtpNode node = new OtpNode("gurka"); </code> </section> <section> + <title>Transport Factory</title> + <p>All necessary connections are made using methods of + <seealso marker="java/com/ericsson/otp/erlang/OtpTransportFactory">OtpTransportFactory</seealso> + interface. Default OtpTransportFactory implementation is based on standard Socket class. + User may provide custom transport factory as needed. See java doc for details.</p> + </section> + + <section> <title>Sending and Receiving Messages</title> <p>Messages sent with this package must be instances of <seealso marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seealso> diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java index 1b0fe3e2e6..ab8fa06c1b 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java @@ -20,7 +20,7 @@ package com.ericsson.otp.erlang; import java.io.IOException; -import java.net.Socket; +import java.io.OutputStream; import java.util.Random; /** @@ -84,7 +84,7 @@ public abstract class AbstractConnection extends Thread { private volatile boolean done = false; protected boolean connected = false; // connection status - protected Socket socket; // communication channel + protected OtpTransport socket; // communication channel protected OtpPeer peer; // who are we connected to protected OtpLocalNode localNode; // this nodes id String name; // local name of this connection @@ -126,7 +126,7 @@ public abstract class AbstractConnection extends Thread { * Accept an incoming connection from a remote node. Used by * {@link OtpSelf#accept() OtpSelf.accept()} to create a connection based on * data received when handshaking with the peer node, when the remote node - * is the connection intitiator. + * is the connection initiator. * * @exception java.io.IOException * if it was not possible to connect to the peer. @@ -134,20 +134,17 @@ public abstract class AbstractConnection extends Thread { * @exception OtpAuthException * if handshake resulted in an authentication error */ - protected AbstractConnection(final OtpLocalNode self, final Socket s) + protected AbstractConnection(final OtpLocalNode self, final OtpTransport s) throws IOException, OtpAuthException { localNode = self; - peer = new OtpPeer(); + peer = new OtpPeer(self.transportFactory); socket = s; - socket.setTcpNoDelay(true); - traceLevel = defaultLevel; setDaemon(true); if (traceLevel >= handshakeThreshold) { - System.out.println("<- ACCEPT FROM " + s.getInetAddress() + ":" - + s.getPort()); + System.out.println("<- ACCEPT FROM " + s); } // get his info @@ -189,6 +186,8 @@ public abstract class AbstractConnection extends Thread { // now get a connection between the two... port = OtpEpmd.lookupPort(peer); + if (port == 0) + throw new IOException("No remote node found - cannot connect"); // now find highest common dist value if (peer.proto != self.proto || self.distHigh < peer.distLow @@ -523,7 +522,9 @@ public abstract class AbstractConnection extends Thread { // received tick? send tock! if (len == 0) { synchronized (this) { - socket.getOutputStream().write(tock); + OutputStream out = socket.getOutputStream(); + out.write(tock); + out.flush(); } } @@ -837,8 +838,11 @@ public abstract class AbstractConnection extends Thread { } } - header.writeTo(socket.getOutputStream()); - payload.writeTo(socket.getOutputStream()); + // group flush op in favour of possible ssh-tunneled stream + OutputStream out = socket.getOutputStream(); + header.writeTo(out); + payload.writeTo(out); + out.flush(); } catch (final IOException e) { close(); throw e; @@ -859,7 +863,7 @@ public abstract class AbstractConnection extends Thread { + e); } } - header.writeTo(socket.getOutputStream()); + header.writeToAndFlush(socket.getOutputStream()); } catch (final IOException e) { close(); throw e; @@ -913,7 +917,8 @@ public abstract class AbstractConnection extends Thread { } /* this method now throws exception if we don't get full read */ - protected int readSock(final Socket s, final byte[] b) throws IOException { + protected int readSock(final OtpTransport s, final byte[] b) + throws IOException { int got = 0; final int len = b.length; int i; @@ -980,8 +985,7 @@ public abstract class AbstractConnection extends Thread { protected void doConnect(final int port) throws IOException, OtpAuthException { try { - socket = new Socket(peer.host(), port); - socket.setTcpNoDelay(true); + socket = peer.createTransport(peer.host(), port); if (traceLevel >= handshakeThreshold) { System.out.println("-> MD5 CONNECT TO " + peer.host() + ":" @@ -1077,7 +1081,7 @@ public abstract class AbstractConnection extends Thread { obuf.write4BE(aflags); obuf.write(str.getBytes()); - obuf.writeTo(socket.getOutputStream()); + obuf.writeToAndFlush(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendName" + " flags=" @@ -1098,7 +1102,7 @@ public abstract class AbstractConnection extends Thread { obuf.write4BE(challenge); obuf.write(str.getBytes()); - obuf.writeTo(socket.getOutputStream()); + obuf.writeToAndFlush(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallenge" + " flags=" @@ -1232,7 +1236,7 @@ public abstract class AbstractConnection extends Thread { obuf.write1(ChallengeReply); obuf.write4BE(challenge); obuf.write(digest); - obuf.writeTo(socket.getOutputStream()); + obuf.writeToAndFlush(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallengeReply" @@ -1294,7 +1298,7 @@ public abstract class AbstractConnection extends Thread { obuf.write1(ChallengeAck); obuf.write(digest); - obuf.writeTo(socket.getOutputStream()); + obuf.writeToAndFlush(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendChallengeAck" @@ -1341,7 +1345,7 @@ public abstract class AbstractConnection extends Thread { obuf.write1(ChallengeStatus); obuf.write(status.getBytes()); - obuf.writeTo(socket.getOutputStream()); + obuf.writeToAndFlush(socket.getOutputStream()); if (traceLevel >= handshakeThreshold) { System.out.println("-> " + "HANDSHAKE sendStatus" + " status=" diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java index 6f07d8171e..0a33984b31 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java @@ -64,13 +64,14 @@ import java.net.UnknownHostException; * instead. * </p> */ -public class AbstractNode { +public class AbstractNode implements OtpTransportFactory { static String localHost = null; String node; String host; String alive; String cookie; static String defaultCookie = null; + final OtpTransportFactory transportFactory; // Node types static final int NTYPE_R6 = 110; // 'n' post-r5, all nodes @@ -146,21 +147,41 @@ public class AbstractNode { } } - protected AbstractNode() { + protected AbstractNode(final OtpTransportFactory transportFactory) { + this.transportFactory = transportFactory; } /** - * Create a node with the given name and the default cookie. + * Create a node with the given name and default cookie and transport + * factory. */ protected AbstractNode(final String node) { - this(node, defaultCookie); + this(node, defaultCookie, new OtpSocketTransportFactory()); } /** - * Create a node with the given name and cookie. + * Create a node with the given name, transport factory and the default + * cookie. + */ + protected AbstractNode(final String node, + final OtpTransportFactory transportFactory) { + this(node, defaultCookie, transportFactory); + } + + /** + * Create a node with the given name, cookie and default transport factory. */ protected AbstractNode(final String name, final String cookie) { + this(name, cookie, new OtpSocketTransportFactory()); + } + + /** + * Create a node with the given name, cookie and transport factory. + */ + protected AbstractNode(final String name, final String cookie, + final OtpTransportFactory transportFactory) { this.cookie = cookie; + this.transportFactory = transportFactory; final int i = name.indexOf('@', 0); if (i < 0) { @@ -268,4 +289,19 @@ public class AbstractNode { } return home; } + + public OtpTransport createTransport(final String addr, final int port) + throws IOException { + return transportFactory.createTransport(addr, port); + } + + public OtpTransport createTransport(final InetAddress addr, final int port) + throws IOException { + return transportFactory.createTransport(addr, port); + } + + public OtpServerTransport createServerTransport(final int port) + throws IOException { + return transportFactory.createServerTransport(port); + } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java index 2c9b7766bc..af0926f939 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java @@ -19,7 +19,6 @@ package com.ericsson.otp.erlang; import java.io.IOException; -import java.net.Socket; /** * Maintains a connection between a Java process and a remote Erlang, Java or C @@ -63,8 +62,8 @@ public class OtpConnection extends AbstractConnection { * error */ // package scope - OtpConnection(final OtpSelf self, final Socket s) throws IOException, - OtpAuthException { + OtpConnection(final OtpSelf self, final OtpTransport s) + throws IOException, OtpAuthException { super(self, s); this.self = self; queue = new GenericQueue(); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java index 4d80f61d52..b0e3e81fca 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java @@ -19,7 +19,6 @@ package com.ericsson.otp.erlang; import java.io.IOException; -import java.net.Socket; /** * <p> @@ -78,8 +77,8 @@ public class OtpCookedConnection extends AbstractConnection { * error */ // package scope - OtpCookedConnection(final OtpNode self, final Socket s) throws IOException, - OtpAuthException { + OtpCookedConnection(final OtpNode self, final OtpTransport s) + throws IOException, OtpAuthException { super(self, s); this.self = self; links = new Links(25); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java index 796babee1b..6c7c8fe951 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java @@ -21,13 +21,12 @@ package com.ericsson.otp.erlang; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetAddress; -import java.net.Socket; /** * Provides methods for registering, unregistering and looking up nodes with the * Erlang portmapper daemon (Epmd). For each registered node, Epmd maintains * information about the port on which incoming connections are accepted, as - * well as which versions of the Erlang communication protocolt the node + * well as which versions of the Erlang communication protocol the node * supports. * * <p> @@ -136,7 +135,7 @@ public class OtpEpmd { */ public static boolean publishPort(final OtpLocalNode node) throws IOException { - Socket s = null; + OtpTransport s = null; s = r4_publish(node); @@ -156,16 +155,16 @@ public class OtpEpmd { * This method does not report any failures. */ public static void unPublishPort(final OtpLocalNode node) { - Socket s = null; + OtpTransport s = null; try { - s = new Socket((String) null, EpmdPort.get()); + s = node.createTransport((String) null, EpmdPort.get()); @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); obuf.write2BE(node.alive().length() + 1); obuf.write1(stopReq); obuf.writeN(node.alive().getBytes()); - obuf.writeTo(s.getOutputStream()); + obuf.writeToAndFlush(s.getOutputStream()); // don't even wait for a response (is there one?) if (traceLevel >= traceThreshold) { System.out.println("-> UNPUBLISH " + node + " port=" @@ -187,12 +186,12 @@ public class OtpEpmd { private static int r4_lookupPort(final AbstractNode node) throws IOException { int port = 0; - Socket s = null; + OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); - s = new Socket(node.host(), EpmdPort.get()); + s = node.createTransport(node.host(), EpmdPort.get()); // build and send epmd request // length[2], tag[1], alivename[n] (length = n+1) @@ -201,7 +200,7 @@ public class OtpEpmd { obuf.writeN(node.alive().getBytes()); // send request - obuf.writeTo(s.getOutputStream()); + obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> LOOKUP (r4) " + node); @@ -242,7 +241,7 @@ public class OtpEpmd { System.out.println("<- (no response)"); } throw new IOException("Nameserver not responding on " + node.host() - + " when looking up " + node.alive()); + + " when looking up " + node.alive(), e); } catch (final OtpErlangDecodeException e) { if (traceLevel >= traceThreshold) { System.out.println("<- (invalid response)"); @@ -276,14 +275,14 @@ public class OtpEpmd { * fatal. If we manage to successfully communicate with an r4 epmd, we * return either the socket, or null, depending on the result. */ - private static Socket r4_publish(final OtpLocalNode node) + private static OtpTransport r4_publish(final OtpLocalNode node) throws IOException { - Socket s = null; + OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); - s = new Socket((String) null, EpmdPort.get()); + s = node.createTransport((String) null, EpmdPort.get()); obuf.write2BE(node.alive().length() + 13); @@ -301,7 +300,7 @@ public class OtpEpmd { obuf.write2BE(0); // No extra // send request - obuf.writeTo(s.getOutputStream()); + obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> PUBLISH (r4) " + node + " port=" @@ -356,23 +355,34 @@ public class OtpEpmd { } public static String[] lookupNames() throws IOException { - return lookupNames(InetAddress.getByName(null)); + return lookupNames(InetAddress.getByName(null), + new OtpSocketTransportFactory()); + } + + public static String[] lookupNames( + final OtpTransportFactory transportFactory) throws IOException { + return lookupNames(InetAddress.getByName(null), transportFactory); } public static String[] lookupNames(final InetAddress address) throws IOException { - Socket s = null; + return lookupNames(address, new OtpSocketTransportFactory()); + } + + public static String[] lookupNames(final InetAddress address, + final OtpTransportFactory transportFactory) throws IOException { + OtpTransport s = null; try { @SuppressWarnings("resource") final OtpOutputStream obuf = new OtpOutputStream(); try { - s = new Socket(address, EpmdPort.get()); + s = transportFactory.createTransport(address, EpmdPort.get()); obuf.write2BE(1); obuf.write1(names4req); // send request - obuf.writeTo(s.getOutputStream()); + obuf.writeToAndFlush(s.getOutputStream()); if (traceLevel >= traceThreshold) { System.out.println("-> NAMES (r4) "); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java index b996ba6f6c..dd1d299297 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java @@ -29,12 +29,7 @@ public class OtpLocalNode extends AbstractNode { private int refId[]; protected int port; - protected java.net.Socket epmd; - - protected OtpLocalNode() { - super(); - init(); - } + protected OtpTransport epmd; /** * Create a node with the given name and the default cookie. @@ -45,6 +40,16 @@ public class OtpLocalNode extends AbstractNode { } /** + * Create a node with the given name, transport factory and the default + * cookie. + */ + protected OtpLocalNode(final String node, + final OtpTransportFactory transportFactory) { + super(node, transportFactory); + init(); + } + + /** * Create a node with the given name and cookie. */ protected OtpLocalNode(final String node, final String cookie) { @@ -52,6 +57,15 @@ public class OtpLocalNode extends AbstractNode { init(); } + /** + * Create a node with the given name, cookie and transport factory. + */ + protected OtpLocalNode(final String node, final String cookie, + final OtpTransportFactory transportFactory) { + super(node, cookie, transportFactory); + init(); + } + private void init() { serial = 0; pidCount = 1; @@ -77,7 +91,7 @@ public class OtpLocalNode extends AbstractNode { * @param s * The socket connecting this node to Epmd. */ - protected void setEpmd(final java.net.Socket s) { + protected void setEpmd(final OtpTransport s) { epmd = s; } @@ -86,7 +100,7 @@ public class OtpLocalNode extends AbstractNode { * * @return The socket connecting this node to Epmd. */ - protected java.net.Socket getEpmd() { + protected OtpTransport getEpmd() { return epmd; } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java index d5edd135cf..7512d34c21 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java @@ -20,8 +20,6 @@ package com.ericsson.otp.erlang; import java.io.IOException; import java.lang.ref.WeakReference; -import java.net.ServerSocket; -import java.net.Socket; import java.util.Collection; import java.util.Enumeration; import java.util.Hashtable; @@ -97,7 +95,39 @@ public class OtpNode extends OtpLocalNode { * */ public OtpNode(final String node) throws IOException { - this(node, defaultCookie, 0); + super(node); + + init(0); + } + + /** + * <p> + * Create a node using the default cookie. The default cookie is found by + * reading the first line of the .erlang.cookie file in the user's home + * directory. The home directory is obtained from the System property + * "user.home". + * </p> + * + * <p> + * If the file does not exist, an empty string is used. This method makes no + * attempt to create the file. + * </p> + * + * @param node + * the name of this node. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node, + final OtpTransportFactory transportFactory) throws IOException { + super(node, transportFactory); + + init(0); } /** @@ -128,6 +158,28 @@ public class OtpNode extends OtpLocalNode { * the authorization cookie that will be used by this node when * it communicates with other nodes. * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node, final String cookie, + final OtpTransportFactory transportFactory) throws IOException { + this(node, cookie, 0, transportFactory); + } + + /** + * Create a node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * * @param port * the port number you wish to use for incoming connections. * Specifying 0 lets the system choose an available port. @@ -143,6 +195,34 @@ public class OtpNode extends OtpLocalNode { init(port); } + /** + * Create a node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @param port + * the port number you wish to use for incoming connections. + * Specifying 0 lets the system choose an available port. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node, final String cookie, final int port, + final OtpTransportFactory transportFactory) throws IOException { + super(node, cookie, transportFactory); + + init(port); + } + private synchronized void init(final int aport) throws IOException { if (!initDone) { connections = new Hashtable<String, OtpCookedConnection>(17, @@ -681,12 +761,12 @@ public class OtpNode extends OtpLocalNode { * this thread simply listens for incoming connections */ public class Acceptor extends Thread { - private final ServerSocket sock; + private final OtpServerTransport sock; private final int acceptorPort; private volatile boolean done = false; Acceptor(final int port) throws IOException { - sock = new ServerSocket(port); + sock = createServerTransport(port); acceptorPort = sock.getLocalPort(); OtpNode.this.port = acceptorPort; @@ -720,7 +800,7 @@ public class OtpNode extends OtpLocalNode { localStatus(node, false, null); } - private void closeSock(final ServerSocket s) { + private void closeSock(final OtpServerTransport s) { try { if (s != null) { s.close(); @@ -729,7 +809,7 @@ public class OtpNode extends OtpLocalNode { } } - private void closeSock(final Socket s) { + private void closeSock(final OtpTransport s) { try { if (s != null) { s.close(); @@ -744,7 +824,7 @@ public class OtpNode extends OtpLocalNode { @Override public void run() { - Socket newsock = null; + OtpTransport newsock = null; OtpCookedConnection conn = null; localStatus(node, true, null); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java index b8493b57ff..2ec583ff5c 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -21,6 +21,7 @@ package com.ericsson.otp.erlang; // import java.io.OutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; @@ -202,6 +203,16 @@ public class OtpOutputStream extends ByteArrayOutputStream { super.count += len; } + @Override + public synchronized void writeTo(OutputStream out) throws IOException { + super.writeTo(out); + } + + public synchronized void writeToAndFlush(OutputStream out) throws IOException { + super.writeTo(out); + out.flush(); + } + /** * Write the low byte of a value to the stream. * @@ -887,7 +898,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { if (oos.size() < 5) { // fast path for small terms try { - oos.writeTo(this); + oos.writeToAndFlush(this); // if the term is written as a compressed term, the output // stream is closed, so we do this here, too close(); diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java index 2c79c04247..cb09b40f47 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java @@ -32,8 +32,8 @@ public class OtpPeer extends AbstractNode { * common protocol version we both support */ - OtpPeer() { - super(); + OtpPeer(final OtpTransportFactory transportFactory) { + super(transportFactory); } /** @@ -47,6 +47,19 @@ public class OtpPeer extends AbstractNode { } /** + * Create a peer node with custom transport factory. + * + * @param node + * the name of the node. + * @param transportFactory + * custom transport factory + */ + public OtpPeer(final String node, final OtpTransportFactory + transportFactory) { + super(node, transportFactory); + } + + /** * Create a connection to a remote node. * * @param self diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java index 166dac5701..5b9d13ad81 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java @@ -19,8 +19,6 @@ package com.ericsson.otp.erlang; import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; import java.net.UnknownHostException; /** @@ -48,7 +46,7 @@ import java.net.UnknownHostException; * */ public class OtpSelf extends OtpLocalNode { - private final ServerSocket sock; + private final OtpServerTransport sock; private final OtpErlangPid pid; /** @@ -67,12 +65,43 @@ public class OtpSelf extends OtpLocalNode { * @param node * the name of this node. * + * @exception IOException + * in case of server transport failure + * */ public OtpSelf(final String node) throws IOException { this(node, defaultCookie, 0); } /** + * <p> + * Create a self node using the default cookie and custom transport factory. + * The default cookie is found by reading the first line of the + * .erlang.cookie file in the user's home directory. The home directory is + * obtained from the System property "user.home". + * </p> + * + * <p> + * If the file does not exist, an empty string is used. This method makes no + * attempt to create the file. + * </p> + * + * @param node + * the name of this node. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * in case of server transport failure + * + */ + public OtpSelf(final String node, + final OtpTransportFactory transportFactory) throws IOException { + this(node, defaultCookie, 0, transportFactory); + } + + /** * Create a self node. * * @param node @@ -81,16 +110,95 @@ public class OtpSelf extends OtpLocalNode { * @param cookie * the authorization cookie that will be used by this node when * it communicates with other nodes. + * + * @exception IOException + * in case of server transport failure */ public OtpSelf(final String node, final String cookie) throws IOException { this(node, cookie, 0); } + /** + * Create a self node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * in case of server transport failure + */ + public OtpSelf(final String node, final String cookie, + final OtpTransportFactory transportFactory) throws IOException { + this(node, cookie, 0, transportFactory); + } + + /** + * Create a self node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @param port + * the port number you wish to use for incoming connections. + * Specifying 0 lets the system choose an available port. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * in case of server transport failure + */ public OtpSelf(final String node, final String cookie, final int port) throws IOException { super(node, cookie); - sock = new ServerSocket(port); + sock = createServerTransport(port); + + if (port != 0) { + this.port = port; + } else { + this.port = sock.getLocalPort(); + } + + pid = createPid(); + } + + /** + * Create a self node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @param port + * the port number you wish to use for incoming connections. + * Specifying 0 lets the system choose an available port. + * + * @param transportFactory + * the transport factory to use when creating connections. + * + * @exception IOException + * in case of server transport failure + */ + public OtpSelf(final String node, final String cookie, final int port, + final OtpTransportFactory transportFactory) throws IOException { + super(node, cookie, transportFactory); + + sock = createServerTransport(port); if (port != 0) { this.port = port; @@ -179,7 +287,7 @@ public class OtpSelf extends OtpLocalNode { * authorized to connect. */ public OtpConnection accept() throws IOException, OtpAuthException { - Socket newsock = null; + OtpTransport newsock = null; while (true) { try { diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerSocketTransport.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerSocketTransport.java new file mode 100644 index 0000000000..0e25b6bfb7 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerSocketTransport.java @@ -0,0 +1,68 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * Default socket-based server transport + * + * @author Dmitriy Kargapolov + */ +public class OtpServerSocketTransport implements OtpServerTransport { + + /** + * Underlying server socket + */ + private final ServerSocket socket; + + /** + * @see ServerSocket#ServerSocket(int) + */ + public OtpServerSocketTransport(final int port) throws IOException { + socket = new ServerSocket(port); + } + + /** + * @see ServerSocket#getLocalPort() + */ + public int getLocalPort() { + return socket.getLocalPort(); + } + + /** + * @see ServerSocket#accept() + */ + public OtpTransport accept() throws IOException { + final Socket sock = socket.accept(); + sock.setTcpNoDelay(true); + return new OtpSocketTransport(sock); + } + + /** + * @see ServerSocket#close() + */ + public void close() throws IOException { + socket.close(); + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerTransport.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerTransport.java new file mode 100644 index 0000000000..4d31380bee --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServerTransport.java @@ -0,0 +1,46 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.ServerSocket; + +/** + * Server-side connection-oriented transport interface. + * + * @author Dmitriy Kargapolov + */ +public interface OtpServerTransport { + + /** + * @see ServerSocket#getLocalPort() + */ + int getLocalPort(); + + /** + * @see ServerSocket#accept() + */ + OtpTransport accept() throws IOException; + + /** + * @see ServerSocket#close() + */ + void close() throws IOException; +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransport.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransport.java new file mode 100644 index 0000000000..f690ab59ed --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransport.java @@ -0,0 +1,89 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * Default socket-based client transport + * + * @author Dmitriy Kargapolov + */ +public class OtpSocketTransport implements OtpTransport { + + /** + * Underlying socket + */ + private final Socket socket; + + /** + * @see Socket#Socket(String, int) + */ + public OtpSocketTransport(final String addr, final int port) + throws UnknownHostException, IOException { + socket = new Socket(addr, port); + socket.setTcpNoDelay(true); + } + + /** + * @see Socket#Socket(InetAddress, int) + */ + public OtpSocketTransport(final InetAddress addr, final int port) + throws UnknownHostException, IOException { + socket = new Socket(addr, port); + socket.setTcpNoDelay(true); + } + + /** + * Socket wrapping constructor + * + * @param s + * socket to wrap + */ + public OtpSocketTransport(final Socket s) { + socket = s; + } + + /** + * @see Socket#getInputStream() + */ + public InputStream getInputStream() throws IOException { + return socket.getInputStream(); + } + + /** + * @see Socket#getOutputStream() + */ + public OutputStream getOutputStream() throws IOException { + return socket.getOutputStream(); + } + + /** + * @see Socket#close() + */ + public void close() throws IOException { + socket.close(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransportFactory.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransportFactory.java new file mode 100644 index 0000000000..f6b5bfc86d --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSocketTransportFactory.java @@ -0,0 +1,56 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.InetAddress; + +/** + * Default socket-based transport factory + * + * @author Dmitriy Kargapolov + */ +public class OtpSocketTransportFactory implements OtpTransportFactory { + + /** + * @see OtpTransportFactory#createTransport(String, int) + */ + public OtpTransport createTransport(final String addr, final int port) + throws IOException { + return new OtpSocketTransport(addr, port); + } + + /** + * @see OtpTransportFactory#createTransport(InetAddress, int) + */ + public OtpTransport createTransport(final InetAddress addr, final int port) + throws IOException { + return new OtpSocketTransport(addr, port); + } + + /** + * @see OtpTransportFactory#createServerTransport(int) + */ + public OtpServerTransport createServerTransport(final int port) + throws IOException { + return new OtpServerSocketTransport(port); + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransport.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransport.java new file mode 100644 index 0000000000..51c62d9ef0 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransport.java @@ -0,0 +1,49 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * Client-side connection-oriented transport interface. + * + * @author Dmitriy Kargapolov + */ +public interface OtpTransport { + + /** + * @see Socket#getInputStream() + */ + public abstract InputStream getInputStream() throws IOException; + + /** + * @see Socket#getOutputStream() + */ + public abstract OutputStream getOutputStream() throws IOException; + + /** + * @see Socket#close() + */ + public abstract void close() throws IOException; + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransportFactory.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransportFactory.java new file mode 100644 index 0000000000..bd404daea5 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpTransportFactory.java @@ -0,0 +1,124 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.InetAddress; + +/** + * Factory class used to create client- and server-side transport instances. One + * static instance of class implementing this interface is created when program + * loaded. Default implementation used is {@link OtpSocketTransportFactory}. + * JInterface user can specify custom transport factory implementing this + * interface in the following ways: + * <dl> + * <dt>defining static class as internal to class holding main() method</dt> + * <dd>In the systems, where main class can be retrieved with + * <code>System.getProperty("sun.java.command")</code>, user can define static + * class <b>OtpErlangSystemTuner</b> internal to the main class, providing at + * least one static method with the name <b>getOtpTransportFactory</b>, with no + * parameters, returning object of class implementing + * <b>OtpTransportFactory</b>, for example: + * + * <pre> + * + * public class MyMainClass { + * + * public static class OtpErlangSystemTuner { + * ... + * public static OtpTransportFactory getOtpTransportFactory() { + * return new MyTransportFactory(); + * } + * } + * + * public static class MyTransportFactory implements OtpTransportFactory { + * ... + * } + * + * public static void main(String[] args) { + * ... + * } + * } + * + * + * </pre> + * + * </dd> + * + * <dt>specifying factory class in the system properties</dt> + * <dd>User-defined transport factory class may be specified via system property + * <b>OtpTransportFactory</b>, for example: + * + * <pre> + * + * package com.my.company; + * + * public static class MyTransportFactory implements OtpTransportFactory { + * ... + * } + * </pre> + * + * In such case program may be run with + * -DOtpTransportFactory=com.my.company.MyTransportFactory, or other way of + * setting system property <i>before execution of static initializers</i> may be + * used.</dd> + * </dl> + * + * @author Dmitriy Kargapolov + */ +public interface OtpTransportFactory { + + /** + * Create instance of {@link OtpTransport} + * + * @param addr + * host name or IP address string + * @param port + * port number + * @return new socket object + * @throws IOException + */ + public abstract OtpTransport createTransport(String addr, int port) + throws IOException; + + /** + * Create instance of {@link OtpTransport} + * + * @param addr + * peer address + * @param port + * port number + * @return new socket object + * @throws IOException + */ + public abstract OtpTransport createTransport(InetAddress addr, int port) + throws IOException; + + /** + * Create instance of {@link OtpServerTransport} + * + * @param port + * port number to listen on + * @return new socket object + * @throws IOException + */ + public OtpServerTransport createServerTransport(int port) + throws IOException; +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files index 62fa7f990e..a0f19bc1aa 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files @@ -53,7 +53,13 @@ COMM = \ OtpOutputStream \ OtpPeer \ OtpSelf \ - OtpServer + OtpServer \ + OtpServerSocketTransport \ + OtpServerTransport \ + OtpSocketTransport \ + OtpSocketTransportFactory \ + OtpTransport \ + OtpTransportFactory ERL = \ OtpErlangAtom \ diff --git a/lib/jinterface/test/jinterface_SUITE.erl b/lib/jinterface/test/jinterface_SUITE.erl index 00abc97ff5..3743e763c5 100644 --- a/lib/jinterface/test/jinterface_SUITE.erl +++ b/lib/jinterface/test/jinterface_SUITE.erl @@ -22,7 +22,8 @@ init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2]). --export([nodename/1, register_and_whereis/1, get_names/1, boolean_atom/1, +-export([transport_factory/1, + nodename/1, register_and_whereis/1, get_names/1, boolean_atom/1, node_ping/1, mbox_ping/1, java_erlang_send_receive/1, java_internal_send_receive_same_node/1, @@ -103,6 +104,7 @@ end_per_group(_GroupName, Config) -> fundamental() -> [ + transport_factory, % TransportFactoryTest.java nodename, % Nodename.java register_and_whereis, % RegisterAndWhereis.java get_names, % GetNames.java @@ -201,6 +203,16 @@ end_per_testcase(_Case,Config) -> %%%----------------------------------------------------------------- %%% TEST CASES %%%----------------------------------------------------------------- +transport_factory(doc) -> + ["TransportFactoryTest.java: Test custom OTP Transport Factory"]; +transport_factory(suite) -> + []; +transport_factory(Config) when is_list(Config) -> + ok = jitu:java(?config(java, Config), + ?config(data_dir, Config), + "TransportFactoryTest"). + +%%%----------------------------------------------------------------- nodename(doc) -> ["Nodename.java: " "Test OtpNode.node(), OtpNode.alive() and OtpNode.host()"]; diff --git a/lib/jinterface/test/jinterface_SUITE_data/Makefile.src b/lib/jinterface/test/jinterface_SUITE_data/Makefile.src index cd68f1ead5..eca043913e 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/Makefile.src +++ b/lib/jinterface/test/jinterface_SUITE_data/Makefile.src @@ -38,6 +38,7 @@ JINTERFACE_CLASSPATH = @jinterface_classpath@ CLASSPATH = .@PS@$(JINTERFACE_CLASSPATH)@PS@ JAVA_FILES = \ + TransportFactoryTest.java \ Nodename.java \ RegisterAndWhereis.java \ GetNames.java \ diff --git a/lib/jinterface/test/jinterface_SUITE_data/TransportFactoryTest.java b/lib/jinterface/test/jinterface_SUITE_data/TransportFactoryTest.java new file mode 100644 index 0000000000..367e28a512 --- /dev/null +++ b/lib/jinterface/test/jinterface_SUITE_data/TransportFactoryTest.java @@ -0,0 +1,90 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. 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% + */ + +import java.io.IOException; +import java.net.InetAddress; + +import com.ericsson.otp.erlang.OtpSelf; +import com.ericsson.otp.erlang.OtpServerTransport; +import com.ericsson.otp.erlang.OtpSocketTransportFactory; +import com.ericsson.otp.erlang.OtpTransport; +import com.ericsson.otp.erlang.OtpTransportFactory; + +/** + * @author Dmitriy Kargapolov + */ +public class TransportFactoryTest { + + /** + * example of custom transport factory wrapping default one + */ + public static class TransportFactory implements OtpTransportFactory { + + OtpSocketTransportFactory tf = new OtpSocketTransportFactory(); + + public OtpTransport createTransport(final String addr, final int port) + throws IOException { + clientOk = true; + System.out.println("creating transport to " + addr + ", " + port); + return tf.createTransport(addr, port); + } + + public OtpTransport createTransport(final InetAddress addr, + final int port) throws IOException { + clientOk = true; + System.out.println("creating transport to " + addr + ", " + port); + return tf.createTransport(addr, port); + } + + public OtpServerTransport createServerTransport(final int port) + throws IOException { + serverOk = true; + System.out.println("creating server transport to " + port); + return tf.createServerTransport(port); + } + + } + + static boolean serverOk = false; + static boolean clientOk = false; + + public static void main(final String[] args) throws IOException { + + // check server transport + final OtpSelf self = new OtpSelf("local", new TransportFactory()); + if (!serverOk) { + fail("custom server transport was not created"); + } + System.out.println("accepting connections on " + self.port()); + + // check client transport + try { + self.publishPort(); + } catch (final Exception e) { + } + if (!clientOk) { + fail("custom client transport was not created"); + } + } + + private static void fail(final String string) { + System.err.println(string); + System.exit(1); + } +} diff --git a/lib/jinterface/test/jitu.erl b/lib/jinterface/test/jitu.erl index b68dfd0351..8097237af6 100644 --- a/lib/jinterface/test/jitu.erl +++ b/lib/jinterface/test/jitu.erl @@ -117,7 +117,7 @@ classpath(Dir) -> end, es(Dir++PS++ filename:join([code:lib_dir(jinterface),"priv","OtpErlang.jar"])++PS++ - os:getenv("CLASSPATH", "") end, + os:getenv("CLASSPATH", ""), Quote, EscSpace). diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml index 3ec33d2f18..a424d2978e 100644 --- a/lib/kernel/doc/src/heart.xml +++ b/lib/kernel/doc/src/heart.xml @@ -78,6 +78,16 @@ <pre> % <input>erl -heart -env ERL_CRASH_DUMP_SECONDS 10 ...</input></pre> + + <p> If a regular core dump is wanted, let heart know by setting the kill signal to abort + using the environment variable <c><![CDATA[HEART_KILL_SIGNAL=SIGABRT]]></c>. + If unset, or not set to <c><![CDATA[SIGABRT]]></c>, the default behaviour will be a kill + signal using <c><![CDATA[SIGKILL]]></c>. + </p> + + <pre> +% <input>erl -heart -env HEART_KILL_SIGNAL SIGABRT ...</input></pre> + <p> Furthermore, <c><![CDATA[ERL_CRASH_DUMP_SECONDS]]></c> has the following behaviour on <c>heart</c>: diff --git a/lib/percept/src/percept.erl b/lib/percept/src/percept.erl index 3a2d9f7601..135e20774e 100644 --- a/lib/percept/src/percept.erl +++ b/lib/percept/src/percept.erl @@ -319,10 +319,6 @@ get_webserver_config(Servername, Port) when is_list(Servername), is_integer(Port {alias,{"/images/", filename:join([Root, "images"]) ++ "/"}}, {alias,{"/css/", filename:join([Root, "css"]) ++ "/"}}, - % Logs - %{transfer_log, filename:join([Path, "logs", "transfer.log"])}, - %{error_log, filename:join([Path, "logs", "error.log"])}, - % Configs {default_type,"text/plain"}, {directory_index,["index.html"]}, @@ -331,12 +327,9 @@ get_webserver_config(Servername, Port) when is_list(Servername), is_integer(Port mod_esi, mod_actions, mod_cgi, - mod_include, mod_dir, mod_get, mod_head - % mod_log, - % mod_disk_log ]}, {com_type,ip_comm}, {server_name, Servername}, diff --git a/lib/public_key/asn1/Makefile b/lib/public_key/asn1/Makefile index c1b3bc866d..11b03dc2f7 100644 --- a/lib/public_key/asn1/Makefile +++ b/lib/public_key/asn1/Makefile @@ -66,7 +66,7 @@ EBIN = ../ebin EXTRA_ERLC_FLAGS = ERL_COMPILE_FLAGS += $(EXTRA_ERLC_FLAGS) -ASN_FLAGS = -bber +der +compact_bit_string +noobj +asn1config +ASN_FLAGS = -bber +der +noobj +asn1config # ---------------------------------------------------- # Targets diff --git a/lib/public_key/doc/src/cert_records.xml b/lib/public_key/doc/src/cert_records.xml index b66c66bead..857a39bf40 100644 --- a/lib/public_key/doc/src/cert_records.xml +++ b/lib/public_key/doc/src/cert_records.xml @@ -98,7 +98,7 @@ semantics, please see <url #'Certificate'{ tbsCertificate, % #'TBSCertificate'{} signatureAlgorithm, % #'AlgorithmIdentifier'{} - signature % {0, binary()} - ASN1 compact bitstring + signature % bitstring() }. #'TBSCertificate'{ @@ -124,7 +124,7 @@ semantics, please see <url #'OTPCertificate'{ tbsCertificate, % #'OTPTBSCertificate'{} signatureAlgorithm, % #'SignatureAlgorithm' - signature % {0, binary()} - ASN1 compact bitstring + signature % bitstring() }. #'OTPTBSCertificate'{ @@ -542,7 +542,7 @@ oid names see table below. Ex: ?'id-dsa-with-sha1'</p> #'CertificateList'{ tbsCertList, % #'TBSCertList{} signatureAlgorithm, % #'AlgorithmIdentifier'{} - signature % {0, binary()} - ASN1 compact bitstring + signature % bitstring() }). #'TBSCertList'{ @@ -654,7 +654,7 @@ oid names see table below. Ex: ?'id-dsa-with-sha1'</p> #'CertificationRequest'{ certificationRequestInfo #'CertificationRequestInfo'{}, signatureAlgorithm #'CertificationRequest_signatureAlgorithm'{}}. - signature {0, binary()} - ASN1 compact bitstring + signature bitstring() } #'CertificationRequestInfo'{ @@ -666,7 +666,7 @@ oid names see table below. Ex: ?'id-dsa-with-sha1'</p> #'CertificationRequestInfo_subjectPKInfo'{ algorithm #'CertificationRequestInfo_subjectPKInfo_algorithm'{} - subjectPublicKey {0, binary()} - ASN1 compact bitstring + subjectPublicKey bitstring() } #'CertificationRequestInfo_subjectPKInfo_algorithm'{ diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml index d3534846fa..a7dfc41449 100644 --- a/lib/public_key/doc/src/public_key_records.xml +++ b/lib/public_key/doc/src/public_key_records.xml @@ -115,7 +115,7 @@ <code> #'ECPrivateKey'{ version, % integer() - privateKey, % octet_string() + privateKey, % binary() parameters, % der_encoded() - {'EcpkParameters', #'ECParameters'{}} | {'EcpkParameters', {namedCurve, oid()}} | {'EcpkParameters', 'NULL'} % Inherited by CA @@ -126,14 +126,14 @@ version, % integer() fieldID, % #'FieldID'{} curve, % #'Curve'{} - base, % octet_string() + base, % binary() order, % integer() cofactor % integer() }. #'Curve'{ - a, % octet_string() - b, % octet_string() + a, % binary() + b, % binary() seed % bitstring() - optional }. @@ -144,7 +144,7 @@ }. #'ECPoint'{ - point % octet_string() - the public key + point % binary() - the public key }. </code> diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index 8b11538499..1aa9c6764b 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -445,7 +445,7 @@ extensions_list(Extensions) -> Extensions. extract_verify_data(OtpCert, DerCert) -> - {_, Signature} = OtpCert#'OTPCertificate'.signature, + Signature = OtpCert#'OTPCertificate'.signature, SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm, SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm, PlainText = encoded_tbs_cert(DerCert), diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl index 9a8e49f265..f412d5862e 100644 --- a/lib/public_key/src/pubkey_cert_records.erl +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -217,8 +217,8 @@ namedCurves(brainpoolP512t1) -> ?'brainpoolP512t1'. %%% SubjectPublicKey decode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = - #'PublicKeyAlgorithm'{algorithm=Algo}, - subjectPublicKey = {0,SPK0}}) -> + #'PublicKeyAlgorithm'{algorithm=Algo}, + subjectPublicKey = SPK0}) -> Type = supportedPublicKeyAlgorithms(Algo), SPK = case Type of 'ECPoint' -> #'ECPoint'{point = SPK0}; @@ -238,7 +238,7 @@ encode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = {ok, SPK1} = 'OTP-PUB-KEY':encode(Type, SPK0), SPK1 end, - #'OTPSubjectPublicKeyInfo'{subjectPublicKey = {0,SPK}, algorithm=PA}. + #'OTPSubjectPublicKeyInfo'{subjectPublicKey = SPK, algorithm=PA}. %%% Extensions diff --git a/lib/public_key/src/pubkey_crl.erl b/lib/public_key/src/pubkey_crl.erl index 488cc97c70..0010725da9 100644 --- a/lib/public_key/src/pubkey_crl.erl +++ b/lib/public_key/src/pubkey_crl.erl @@ -473,7 +473,7 @@ check_crl_num(_,_) -> extension_value(Extension, ExtType, Extensions) -> case pubkey_cert:select_extension(Extension, Extensions) of #'Extension'{extnValue = Value} -> - public_key:der_decode(ExtType, list_to_binary(Value)); + public_key:der_decode(ExtType, iolist_to_binary(Value)); _ -> undefined end. @@ -565,7 +565,7 @@ verify_crl_signature(CRL, DerCRL, Key, KeyParams) -> {Key, KeyParams}) end. extract_crl_verify_data(CRL, DerCRL) -> - {0, Signature} = CRL#'CertificateList'.signature, + Signature = CRL#'CertificateList'.signature, #'AlgorithmIdentifier'{algorithm = SigAlg} = CRL#'CertificateList'.signatureAlgorithm, PlainText = encoded_tbs_crl(DerCRL), diff --git a/lib/public_key/src/pubkey_pbe.erl b/lib/public_key/src/pubkey_pbe.erl index 521a32189d..8c61bc71d4 100644 --- a/lib/public_key/src/pubkey_pbe.erl +++ b/lib/public_key/src/pubkey_pbe.erl @@ -106,9 +106,8 @@ pbdkdf2(Password, Salt, Count, DerivedKeyLen, Prf, PrfHash, PrfOutputLen)-> %%-------------------------------------------------------------------- decrypt_parameters(#'EncryptedPrivateKeyInfo_encryptionAlgorithm'{ algorithm = Oid, parameters = Param}) -> - decrypt_parameters(Oid, Param). + decrypt_parameters(Oid, decode_handle_open_type_wrapper(Param)). - %%-------------------------------------------------------------------- -spec encrypt_parameters({Cipher::string(), Params::term()}) -> #'EncryptedPrivateKeyInfo_encryptionAlgorithm'{}. @@ -129,7 +128,7 @@ password_to_key_and_iv(Password, _, #'PBES2-params'{} = Params) -> password_to_key_and_iv(Password, _Cipher, {#'PBEParameter'{salt = Salt, iterationCount = Count}, Hash}) -> <<Key:8/binary, IV:8/binary, _/binary>> - = pbdkdf1(Password, erlang:iolist_to_binary(Salt), Count, Hash), + = pbdkdf1(Password, Salt, Count, Hash), {Key, IV}; password_to_key_and_iv(Password, Cipher, Salt) -> KeyLen = derived_key_length(Cipher, undefined), @@ -151,15 +150,15 @@ do_pbdkdf1(Prev, Count, Acc, Hash) -> do_pbdkdf1(Result, Count-1 , <<Result/binary, Acc/binary>>, Hash). iv(#'PBES2-params_encryptionScheme'{algorithm = Algo, - parameters = ASNIV}) when (Algo == ?'desCBC') or - (Algo == ?'des-EDE3-CBC') -> - %% This is an so called open ASN1-type that in this - %% case will be an octet-string of length 8 - <<?ASN1_OCTET_STR_TAG, ?IV_LEN, IV:?IV_LEN/binary>> = ASNIV, + parameters = ASN1IV}) + when (Algo == ?'desCBC') or + (Algo == ?'des-EDE3-CBC') -> + <<?ASN1_OCTET_STR_TAG, ?IV_LEN, IV:?IV_LEN/binary>> = decode_handle_open_type_wrapper(ASN1IV), IV; iv(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC', - parameters = ASN1IV}) -> - {ok, #'RC2-CBC-Parameter'{iv = IV}} = 'PKCS-FRAME':decode('RC2-CBC-Parameter', ASN1IV), + parameters = ASN1IV}) -> + {ok, #'RC2-CBC-Parameter'{iv = IV}} + = 'PKCS-FRAME':decode('RC2-CBC-Parameter', decode_handle_open_type_wrapper(ASN1IV)), iolist_to_binary(IV). blocks(1, N, Index, Password, Salt, Count, Prf, PrfHash, PrfLen, Acc) -> @@ -200,13 +199,13 @@ encrypt_parameters(_Cipher, #'PBES2-params'{} = Params) -> {ok, Der} ='PKCS-FRAME':encode('PBES2-params', Params), #'EncryptedPrivateKeyInfo_encryptionAlgorithm'{ algorithm = ?'id-PBES2', - parameters = Der}; + parameters = encode_handle_open_type_wrapper(Der)}; encrypt_parameters(Cipher, {#'PBEParameter'{} = Params, Hash}) -> {ok, Der} ='PKCS-FRAME':encode('PBEParameter', Params), #'EncryptedPrivateKeyInfo_encryptionAlgorithm'{ algorithm = pbe1_oid(Cipher, Hash), - parameters = Der}. + parameters = encode_handle_open_type_wrapper(Der)}. pbe1_oid("RC2-CBC", sha) -> ?'pbeWithSHA1AndRC2-CBC'; @@ -277,3 +276,8 @@ cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC'}) -> ceiling(Float) -> erlang:round(Float + 0.5). + +decode_handle_open_type_wrapper({asn1_OPENTYPE, Type}) -> + Type. +encode_handle_open_type_wrapper(Type) -> + {asn1_OPENTYPE, Type}. diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl index 98881c4a6a..a62658923f 100644 --- a/lib/public_key/src/pubkey_pem.erl +++ b/lib/public_key/src/pubkey_pem.erl @@ -143,8 +143,7 @@ decode_encrypted_private_keyinfo(Der) -> encryptedData = Data} = public_key:der_decode('EncryptedPrivateKeyInfo', Der), DecryptParams = pubkey_pbe:decrypt_parameters(AlgorithmInfo), - {'PrivateKeyInfo', iolist_to_binary(Data), DecryptParams}. - + {'PrivateKeyInfo', Data, DecryptParams}. encode_encrypted_private_keyinfo(EncData, EncryptParmams) -> AlgorithmInfo = pubkey_pbe:encrypt_parameters(EncryptParmams), diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index e8ff965982..261054637d 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -120,7 +120,7 @@ pem_encode(PemEntries) when is_list(PemEntries) -> %% pem entries. %%-------------------------------------------------------------------- pem_entry_decode({'SubjectPublicKeyInfo', Der, _}) -> - {_, {'AlgorithmIdentifier', AlgId, Params}, {0, Key0}} + {_, {'AlgorithmIdentifier', AlgId, Params}, Key0} = der_decode('SubjectPublicKeyInfo', Der), KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(AlgId), case KeyType of @@ -168,14 +168,14 @@ pem_entry_decode({Asn1Type, CryptDer, {Cipher, Salt}} = PemEntry, pem_entry_encode('SubjectPublicKeyInfo', Entity=#'RSAPublicKey'{}) -> Der = der_encode('RSAPublicKey', Entity), Spki = {'SubjectPublicKeyInfo', - {'AlgorithmIdentifier', ?'rsaEncryption', ?DER_NULL}, {0, Der}}, + {'AlgorithmIdentifier', ?'rsaEncryption', ?DER_NULL}, Der}, pem_entry_encode('SubjectPublicKeyInfo', Spki); pem_entry_encode('SubjectPublicKeyInfo', {DsaInt, Params=#'Dss-Parms'{}}) when is_integer(DsaInt) -> KeyDer = der_encode('DSAPublicKey', DsaInt), ParamDer = der_encode('DSAParams', {params, Params}), Spki = {'SubjectPublicKeyInfo', - {'AlgorithmIdentifier', ?'id-dsa', ParamDer}, {0, KeyDer}}, + {'AlgorithmIdentifier', ?'id-dsa', ParamDer}, KeyDer}, pem_entry_encode('SubjectPublicKeyInfo', Spki); pem_entry_encode(Asn1Type, Entity) when is_atom(Asn1Type) -> Der = der_encode(Asn1Type, Entity), @@ -234,7 +234,7 @@ der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') or (Asn1Type == 'EncryptedPrivateKeyInfo') -> try {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity), - iolist_to_binary(Encoded) + Encoded catch error:{badmatch, {error, _}} = Error -> erlang:error(Error) @@ -243,7 +243,7 @@ der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') or der_encode(Asn1Type, Entity) when is_atom(Asn1Type) -> try {ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity), - iolist_to_binary(Encoded) + Encoded catch error:{badmatch, {error, _}} = Error -> erlang:error(Error) @@ -391,7 +391,7 @@ generate_key(#'ECParameters'{} = Params) -> compute_key(#'ECPoint'{point = Point}, #'ECPrivateKey'{privateKey = PrivKey, parameters = Param}) -> ECCurve = ec_curve_spec(Param), - crypto:compute_key(ecdh, Point, list_to_binary(PrivKey), ECCurve). + crypto:compute_key(ecdh, Point, PrivKey, ECCurve). compute_key(PubKey, PrivKey, #'DHParameter'{prime = P, base = G}) -> crypto:compute_key(dh, PubKey, PrivKey, [P, G]). @@ -446,7 +446,7 @@ sign(DigestOrPlainText, sha, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> sign(DigestOrPlainText, DigestType, #'ECPrivateKey'{privateKey = PrivKey, parameters = Param}) -> ECCurve = ec_curve_spec(Param), - crypto:sign(ecdsa, DigestType, DigestOrPlainText, [list_to_binary(PrivKey), ECCurve]); + crypto:sign(ecdsa, DigestType, DigestOrPlainText, [PrivKey, ECCurve]); %% Backwards compatible sign(Digest, none, #'DSAPrivateKey'{} = Key) -> @@ -458,22 +458,12 @@ sign(Digest, none, #'DSAPrivateKey'{} = Key) -> | dsa_public_key() | ec_public_key()) -> boolean(). %% Description: Verifies a digital signature. %%-------------------------------------------------------------------- -verify(DigestOrPlainText, DigestType, Signature, - #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}) -> - crypto:verify(rsa, DigestType, DigestOrPlainText, Signature, - [Exp, Mod]); - -verify(DigestOrPlaintext, DigestType, Signature, {#'ECPoint'{point = Point}, Param}) -> - ECCurve = ec_curve_spec(Param), - crypto:verify(ecdsa, DigestType, DigestOrPlaintext, Signature, [Point, ECCurve]); - -%% Backwards compatibility -verify(Digest, none, Signature, {_, #'Dss-Parms'{}} = Key ) -> - verify({digest,Digest}, sha, Signature, Key); - -verify(DigestOrPlainText, sha = DigestType, Signature, {Key, #'Dss-Parms'{p = P, q = Q, g = G}}) - when is_integer(Key), is_binary(Signature) -> - crypto:verify(dss, DigestType, DigestOrPlainText, Signature, [P, Q, G, Key]). +verify(DigestOrPlainText, DigestType, Signature, Key) when is_binary(Signature) -> + do_verify(DigestOrPlainText, DigestType, Signature, Key); +verify(_,_,_,_) -> + %% If Signature is a bitstring and not a binary we know already at this + %% point that the signature is invalid. + false. %%-------------------------------------------------------------------- -spec pkix_dist_point(der_encoded() | #'OTPCertificate'{}) -> @@ -530,7 +520,7 @@ pkix_sign(#'OTPTBSCertificate'{signature = Signature = sign(Msg, DigestType, Key), Cert = #'OTPCertificate'{tbsCertificate= TBSCert, signatureAlgorithm = SigAlg, - signature = {0, Signature} + signature = Signature }, pkix_encode('OTPCertificate', Cert, otp). @@ -753,6 +743,23 @@ ssh_encode(Entries, Type) when is_list(Entries), %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +do_verify(DigestOrPlainText, DigestType, Signature, + #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}) -> + crypto:verify(rsa, DigestType, DigestOrPlainText, Signature, + [Exp, Mod]); + +do_verify(DigestOrPlaintext, DigestType, Signature, {#'ECPoint'{point = Point}, Param}) -> + ECCurve = ec_curve_spec(Param), + crypto:verify(ecdsa, DigestType, DigestOrPlaintext, Signature, [Point, ECCurve]); + +%% Backwards compatibility +do_verify(Digest, none, Signature, {_, #'Dss-Parms'{}} = Key ) -> + verify({digest,Digest}, sha, Signature, Key); + +do_verify(DigestOrPlainText, sha = DigestType, Signature, {Key, #'Dss-Parms'{p = P, q = Q, g = G}}) + when is_integer(Key), is_binary(Signature) -> + crypto:verify(dss, DigestType, DigestOrPlainText, Signature, [P, Q, G, Key]). + do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password) -> Der = der_encode(Asn1Type, Entity), DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password), @@ -985,14 +992,14 @@ ec_generate_key(Params) -> ec_curve_spec( #'ECParameters'{fieldID = FieldId, curve = PCurve, base = Base, order = Order, cofactor = CoFactor }) -> Field = {pubkey_cert_records:supportedCurvesTypes(FieldId#'FieldID'.fieldType), FieldId#'FieldID'.parameters}, - Curve = {erlang:list_to_binary(PCurve#'Curve'.a), erlang:list_to_binary(PCurve#'Curve'.b), none}, - {Field, Curve, erlang:list_to_binary(Base), Order, CoFactor}; + Curve = {PCurve#'Curve'.a, PCurve#'Curve'.b, none}, + {Field, Curve, Base, Order, CoFactor}; ec_curve_spec({namedCurve, OID}) -> pubkey_cert_records:namedCurves(OID). ec_key({PubKey, PrivateKey}, Params) -> #'ECPrivateKey'{version = 1, - privateKey = binary_to_list(PrivateKey), + privateKey = PrivateKey, parameters = Params, - publicKey = {0, PubKey}}. + publicKey = PubKey}. diff --git a/lib/public_key/test/erl_make_certs.erl b/lib/public_key/test/erl_make_certs.erl index b8e0494ce7..668924c03e 100644 --- a/lib/public_key/test/erl_make_certs.erl +++ b/lib/public_key/test/erl_make_certs.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. 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 @@ -114,7 +114,7 @@ verify_signature(DerEncodedCert, DerKey, _KeyParams) -> #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, - parameters = Params, publicKey = {0, PubKey}} -> + parameters = Params, publicKey = PubKey} -> public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) end. @@ -259,9 +259,8 @@ default_extensions(Exts) -> Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, Exts ++ lists:foldl(Filter, Def, Exts). - - extension({_, undefined}) -> []; + extension({basic_constraints, Data}) -> case Data of default -> @@ -278,9 +277,11 @@ extension({basic_constraints, Data}) -> #'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = Data} end; + extension({key_usage, default}) -> #'Extension'{extnID = ?'id-ce-keyUsage', extnValue = [keyCertSign], critical = true}; + extension({Id, Data, Critical}) -> #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. @@ -297,7 +298,7 @@ publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> publickey(#'ECPrivateKey'{version = _Version, privateKey = _PrivKey, parameters = Params, - publicKey = {0, PubKey}}) -> + publicKey = PubKey}) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = #'ECPoint'{point = PubKey}}. @@ -322,14 +323,14 @@ sign_algorithm(#'RSAPrivateKey'{}, Opts) -> {Type, 'NULL'}; sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; -sign_algorithm(#'ECPrivateKey'{}, Opts) -> +sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) -> Type = case proplists:get_value(digest, Opts, sha1) of sha1 -> ?'ecdsa-with-SHA1'; sha512 -> ?'ecdsa-with-SHA512'; sha384 -> ?'ecdsa-with-SHA384'; sha256 -> ?'ecdsa-with-SHA256' end, - {Type, 'NULL'}. + {Type, Parms}. make_key(rsa, _Opts) -> %% (OBS: for testing only) @@ -406,9 +407,9 @@ gen_ec2(CurveId) -> {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), #'ECPrivateKey'{version = 1, - privateKey = binary_to_list(PrivKey), + privateKey = PrivKey, parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, - publicKey = {0, PubKey}}. + publicKey = PubKey}. %% See fips_186-3.pdf dsa_search(T, P0, Q, Iter) when Iter > 0 -> @@ -477,5 +478,3 @@ der_to_pem(File, Entries) -> PemBin = public_key:pem_encode(Entries), file:write_file(File, PemBin). - - diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index 16794660a5..7f752529f0 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 0.23 +PUBLIC_KEY_VSN = 1.0 diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml index b42910cb34..1efbc16016 100644 --- a/lib/ssh/doc/src/introduction.xml +++ b/lib/ssh/doc/src/introduction.xml @@ -25,31 +25,181 @@ <title>Introduction</title> <prepared>OTP team</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> <file>introduction.xml</file> </header> - + <p>SSH is a protocol for secure remote logon and + other secure network services over an insecure network.</p> <section> - <title>Purpose</title> + <title>Scope and Purpose</title> - <p>Secure Shell (SSH) is a protocol for secure remote login and - other secure network services over an insecure network. SSH - provides a single, full-duplex, byte-oriented connection between + <p>SSH provides a single, full-duplex, and byte-oriented connection between client and server. The protocol also provides privacy, integrity, - server authentication and man-in-the-middle protection.</p> - - <p>The Erlang SSH application is an implementation of the SSH - protocol in Erlang which offers API functions to write customized - SSH clients and servers as well as making the Erlang shell - available via SSH. Also included in the SSH application are an - SFTP (SSH File Transfer Protocol) client <seealso - marker="ssh_sftp">ssh_sftp</seealso> and server <seealso - marker="ssh_sftp">ssh_sftpd</seealso>.</p> + server authentication, and man-in-the-middle protection.</p> + + <p>The <c>ssh</c> application is an implementation of the SSH Transport, Connection and Authentication + Layer Protocols in Erlang. It provides the following:</p> + <list type="bulleted"> + <item>API functions to write customized SSH clients and servers applications</item> + <item>The Erlang shell available over SSH</item> + <item>An SFTP client (<seealso marker="ssh_sftp">ssh_sftp</seealso>) + and server (<seealso marker="ssh_sftp">ssh_sftpd</seealso>)</item> + </list> </section> <section> <title>Prerequisites</title> - <p>It is assumed that the reader is familiar with the concepts of <seealso marker="doc/design_principles:des_princ">OTP</seealso> - and has a basic understanding of <url href="http://en.wikipedia.org/wiki/Public-key_cryptography">public keys</url>.</p> + <p>It is assumed that the reader is familiar with the Erlang programming language, + concepts of <em>OTP</em>, and has a basic understanding of <em>public keys</em>.</p> + </section> + +<section> + <title>SSH Protocol Overview</title> + + <p>Conceptually, the SSH protocol can be partitioned into four + layers:</p> + + <image file="SSH_protocols.png"> + <icaption>SSH Protocol Architecture</icaption> + </image> + + <section> + <title>Transport Protocol</title> + + <p>The SSH Transport Protocol is a secure, low-level transport. + It provides strong encryption, cryptographic host + authentication, and integrity protection. A minimum of + Message Authentication Code (MAC) and encryption + algorithms are supported. For details, see the + <seealso marker="ssh">ssh(3)</seealso> manual page in <c>ssh</c>.</p> + </section> + + <section> + <title>Authentication Protocol</title> + + <p>The SSH Authentication Protocol is a general-purpose user + authentication protocol run over the SSH Transport Layer + Protocol. The <c>ssh</c> application supports user authentication as follows: + </p> + <list type="bulleted"> + <item> + Using public key technology. RSA and DSA, X509-certificates + are not supported. + </item> + <item> + Using keyboard-interactive authentication. + This is suitable for interactive authentication methods + that do not need any special software support on the client side. + Instead, all authentication data is entered from the keyboard. + </item> + <item> + Using a pure password-based authentication scheme. + Here, the plain text password is encrypted before sent + over the network. + </item> + </list> + <p>Several configuration options for + authentication handling are available in + <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso> + and <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</p> + <p> + The public key handling can be customized by implementing + the following behaviours from <c>ssh</c>:</p> + <list type="bulleted"> + <item>Module + <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>. + </item> + <item>Module + <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>. + </item> + </list> + </section> + + <section> + <title>Connection Protocol</title> + + <p>The SSH Connection Protocol provides application-support + services over the transport pipe, for example, channel multiplexing, + flow control, remote program execution, signal propagation, and + connection forwarding. Functions for handling the SSH + Connection Protocol can be found in the module <seealso + marker="ssh_connection">ssh_connection</seealso> in <c>ssh</c>. + </p> + </section> + + <section> + <title>Channels</title> + + <p>All terminal sessions, forwarded connections, and so on, are + channels. Multiple channels are multiplexed into a single + connection. All channels are flow-controlled. This means that no + data is sent to a channel peer until a message is received to + indicate that window space is available. + The <em>initial window size</em> specifies how many bytes of channel + data that can be sent to the channel peer without adjusting the + window. Typically, an SSH client opens a channel, sends data (commands), + receives data (control information), and then closes the channel. + The <seealso marker="ssh_channel">ssh_channel</seealso> behaviour + handles generic parts of SSH channel management. This makes it easy + to write your own SSH client/server processes that use flow-control + and thus opens for more focus on the application logic. + </p> + + <p>Channels come in the following three flavors:</p> + + <list type="bulleted"> + <item><em>Subsystem</em> - Named services that can be run as + part of an SSH server, such as SFTP <seealso + marker="ssh_sftpd">(ssh_sftpd)</seealso>, that is built into the + SSH daemon (server) by default, but it can be disabled. The Erlang <c>ssh</c> + daemon can be configured to run any Erlang- + implemented SSH subsystem. + </item> + <item><em>Shell</em> - Interactive shell. By default the + Erlang daemon runs the Erlang shell. The shell can be customized by + providing your own read-eval-print loop. You can also provide your + own Command-Line Interface (CLI) implementation, + but that is much more work. + </item> + <item><em>Exec</em> - One-time remote execution of commands. See function + <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso> + for more information.</item> + </list> + </section> + + + </section> + <section> + <title>Where to Find More Information</title> + <p> + For detailed information about the SSH protocol, refer to the + following Request for Comments(RFCs): + </p> + + <list type="bulleted"> + <item><url href="http://www.ietf.org/rfc/rfc4250.txt">RFC 4250</url> - + Protocol Assigned Numbers</item> + <item><url href="http://www.ietf.org/rfc/rfc4251.txt">RFC 4251</url> - + Protocol Architecture</item> + <item><url href="http://www.ietf.org/rfc/rfc4252.txt">RFC 4252</url> - + Authentication Protocol</item> + <item><url href="http://www.ietf.org/rfc/rfc4253.txt">RFC 4253</url> - + Transport Layer Protocol</item> + <item><url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> - + Connection Protocol</item> + <item><url href="http://www.ietf.org/rfc/rfc4255.txt">RFC 4255</url> - + Key Fingerprints</item> + <item><url href="http://www.ietf.org/rfc/rfc4344.txt">RFC 4344</url> - + Transport Layer Encryption Modes</item> + <item><url href="http://www.ietf.org/rfc/rfc4716.txt">RFC 4716</url> - + Public Key File Format</item> + </list> + </section> </chapter> diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index acbf3124ef..41885c684c 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -29,6 +29,27 @@ <file>notes.xml</file> </header> +<section><title>Ssh 3.2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + New option <c>id_string</c> for <c>ssh:daemon</c> and + <c>ssh:connect</c> for limiting banner grabbing attempts.</p> + <p> + The possible values are: <c>{id_string,string()}</c> and + <c>{id_string,random}</c>. The latter will make ssh + generate a random nonsence id-string for each new + connection.</p> + <p> + Own Id: OTP-12659</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 3.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml index 55339298e8..afe3f2ddf9 100644 --- a/lib/ssh/doc/src/ref_man.xml +++ b/lib/ssh/doc/src/ref_man.xml @@ -28,8 +28,8 @@ <file>ref_man.xml</file> </header> <description> - <p>The SSH application is an erlang implementation of the - secure shell protocol (SSH) as defined by RFC 4250 - 4254</p> + <p>The <c>ssh</c> application is an Erlang implementation of the + Secure Shell Protocol (SSH) as defined by RFC 4250 - 4254.</p> </description> <xi:include href="ssh_app.xml"/> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 0e7e3848ad..d49d3ac2a7 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -22,54 +22,72 @@ </legalnotice> <title>ssh</title> + <prepared></prepared> + <docno></docno> <date>2007-10-06</date> + <rev></rev> </header> <module>ssh</module> - <modulesummary>Main API of the SSH application</modulesummary> + <modulesummary>Main API of the ssh application</modulesummary> <description> - <p>Interface module for the SSH application. </p> + <p>Interface module for the <c>ssh</c> application.</p> </description> <section> <title>SSH</title> <list type="bulleted"> - <item>SSH requires the crypto and public_key applications.</item> - <item>Supported SSH version is 2.0 </item> - <item>Supported MAC algorithms: hmac-sha2-256 and hmac-sha1</item> - <item>Supported encryption algorithms: aes128-ctr, aes128-cb and 3des-cbc</item> - <item>Supports unicode filenames if the emulator and the underlaying OS supports it. See the DESCRIPTION section in <seealso marker="kernel:file">file</seealso> for information about this subject</item> - <item>Supports unicode in shell and cli</item> + <item>For application dependencies see <seealso marker="SSH_app"> ssh(6)</seealso> </item> + <item>Supported SSH version is 2.0.</item> + <item>Supported MAC algorithms: hmac-sha2-256 and hmac-sha1.</item> + <item>Supported encryption algorithms: aes128-ctr, aes128-cb and 3des-cbc.</item> + <item>Supported key exchange algorithms: diffie-hellman-group1-sha1.</item> + <item>Supports unicode filenames if the emulator and the underlaying OS support it. + See section DESCRIPTION in the + <seealso marker="kernel:file">file</seealso> manual page in <c>kernel</c> + for information about this subject.</item> + <item>Supports unicode in shell and CLI.</item> </list> </section> <section> - <title>DATA TYPES </title> + <title>DATA TYPES</title> <p>Type definitions that are used more than once in - this module and/or abstractions to indicate the intended use of the data - type:</p> - <p><c>boolean() = true | false </c></p> - <p><c>string() = [byte()]</c></p> - <p><c>ssh_daemon_ref() - opaque to the user - returned by ssh:daemon/[1,2,3]</c></p> - <p><c>ssh_connection_ref() - opaque to the user - returned by ssh:connect/3</c></p> - <p><c>ip_address() - inet::ip_address()</c></p> - <p><c>subsystem_spec() = {subsystem_name(), - {channel_callback(), channel_init_args()}} </c></p> - <p><c>subsystem_name() = string() </c></p> - <p><c>channel_callback() = atom() - Name of the erlang module - implementing the subsystem using the ssh_channel behavior see</c> - <seealso marker="ssh_channel">ssh_channel(3)</seealso></p> - <p><c>channel_init_args() = list()</c></p> - </section> + this module, or abstractions to indicate the intended use of the data + type, or both:</p> + <taglist> + <tag><c>boolean()</c></tag> + <item><p>= <c>true | false</c></p></item> + <tag><c>string()</c></tag> + <item><p>= <c>[byte()]</c></p></item> + <tag><c>ssh_daemon_ref()</c></tag> + <item><p>Opaque to the user, + returned by <c>ssh:daemon/[1,2,3]</c></p></item> + <tag><c>ssh_connection_ref()</c></tag> + <item><p>Opaque to the user, + returned by <c>ssh:connect/3</c></p></item> + <tag><c>ip_address()</c></tag> + <item><p><c>inet::ip_address</c></p></item> + <tag><c>subsystem_spec()</c></tag> + <item><p>= <c>{subsystem_name(), + {channel_callback(), channel_init_args()}}</c></p></item> + <tag><c>subsystem_name()</c></tag> + <item><p>= <c>string()</c></p></item> + <tag><c>channel_callback()</c></tag> + <item><p>= <c>atom()</c> - Name of the Erlang module + implementing the subsystem using the <c>ssh_channel</c> behavior, see + <seealso marker="ssh_channel">ssh_channel(3)</seealso></p></item> + <tag><c>channel_init_args()</c></tag> + <item><p>= <c>list()</c></p></item> + </taglist> +</section> <funcs> <func> <name>close(ConnectionRef) -> ok </name> - <fsummary>Closes an SSH connection</fsummary> + <fsummary>Closes an SSH connection.</fsummary> <type> <v>ConnectionRef = ssh_connection_ref()</v> </type> @@ -81,135 +99,151 @@ <name>connect(Host, Port, Options) -> </name> <name>connect(Host, Port, Options, Timeout) -> {ok, ssh_connection_ref()} | {error, Reason}</name> - <fsummary>Connect to an ssh server.</fsummary> + <fsummary>Connects to an SSH server.</fsummary> <type> <v>Host = string()</v> <v>Port = integer()</v> - <d>The default is <c><![CDATA[22]]></c>, the assigned well known port + <d><c><![CDATA[22]]></c> is default, the assigned well-known port number for SSH.</d> <v>Options = [{Option, Value}]</v> - <v>Timeout = infinity | integer(milliseconds)</v> - <d>Negotiation timeout, for connection timeout use the option <c>{connect_timeout, timeout()}</c>.</d> + <v>Timeout = infinity | integer()</v> + <d>Negotiation time-out in milli-seconds. The default value is <c>infinity</c>. + For connection time-out, use option <c>{connect_timeout, timeout()}</c>.</d> </type> <desc> <p>Connects to an SSH server. No channel is started. This is done by calling - <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/[2, 4]</seealso>.</p> - <p>Options are:</p> + <seealso marker="ssh_connection#session_channel/2"> + ssh_connection:session_channel/[2, 4]</seealso>.</p> + <p>Options:</p> <taglist> <tag><c><![CDATA[{inet, inet | inet6}]]></c></tag> - <item> IP version to use.</item> + <item> + <p>IP version to use.</p> + </item> <tag><c><![CDATA[{user_dir, string()}]]></c></tag> <item> - <p>Sets the user directory i.e. the directory containing - ssh configuration files for the user such as + <p>Sets the user directory, that is, the directory containing + <c>ssh</c> configuration files for the user, such as <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa, - id_dsa]]></c> and + id_dsa]]></c>, and <c><![CDATA[authorized_key]]></c>. Defaults to the directory normally referred to as - <c><![CDATA[~/.ssh]]></c> </p> + <c><![CDATA[~/.ssh]]></c>.</p> </item> <tag><c><![CDATA[{dsa_pass_phrase, string()}]]></c></tag> <item> - <p>If the user dsa key is protected by a passphrase it can be + <p>If the user DSA key is protected by a passphrase, it can be supplied with this option. </p> </item> <tag><c><![CDATA[{rsa_pass_phrase, string()}]]></c></tag> <item> - <p>If the user rsa key is protected by a passphrase it can be + <p>If the user RSA key is protected by a passphrase, it can be supplied with this option. </p> </item> <tag><c><![CDATA[{silently_accept_hosts, boolean()}]]></c></tag> <item> - <p>When true hosts are added to the + <p>When <c>true</c>, hosts are added to the file <c><![CDATA[known_hosts]]></c> without asking the user. - Defaults to false. + Defaults to <c>false</c>. </p> </item> <tag><c><![CDATA[{user_interaction, boolean()}]]></c></tag> <item> - <p>If false disables the client to connect to the server - if any user interaction is needed such as accepting that - the server will be added to the <c>known_hosts</c> file or - supplying a password. Defaults to true. + <p>If <c>false</c>, disables the client to connect to the server + if any user interaction is needed, such as accepting + the server to be added to the <c>known_hosts</c> file, or + supplying a password. Defaults to <c>true</c>. Even if user interaction is allowed it can be - suppressed by other options such as silently_accept_hosts and - password. Do note that it may not always be desirable to use - those options from a security point of view.</p> + suppressed by other options, such as <c>silently_accept_hosts</c> + and <c>password</c>. However, those optins are not always desirable + to use from a security point of view.</p> </item> <tag><c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></tag> <item> <p>Sets the preferred public key algorithm to use for user - authentication. If the the preferred algorithm fails for - some reason, the other algorithm is tried. The default is + authentication. If the preferred algorithm fails, + the other algorithm is tried. The default is to try <c><![CDATA['ssh-rsa']]></c> first.</p> </item> <tag><c><![CDATA[{pref_public_key_algs, list()}]]></c></tag> <item> - <p>List of public key algorithms to try to use, 'ssh-rsa' and 'ssh-dss' available. - Will override <c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></p> + <p>List of public key algorithms to try to use. + <c>'ssh-rsa'</c> and <c>'ssh-dss'</c> are available. + Overrides <c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></p> </item> <tag><c><![CDATA[{connect_timeout, timeout()}]]></c></tag> <item> - <p>Sets a timeout on the transport layer - connection. Defaults to <c>infinity</c>.</p> + <p>Sets a time-out on the transport layer + connection. For <c>gen_tcp</c> the time is in milli-seconds and the default value is + <c>infinity</c>.</p> </item> <tag><c><![CDATA[{user, string()}]]></c></tag> <item> - <p>Provides a user name. If this option is not given, ssh + <p>Provides a username. If this option is not given, <c>ssh</c> reads from the environment (<c><![CDATA[LOGNAME]]></c> or - <c><![CDATA[USER]]></c> on unix, + <c><![CDATA[USER]]></c> on UNIX, <c><![CDATA[USERNAME]]></c> on Windows).</p> </item> <tag><c><![CDATA[{password, string()}]]></c></tag> <item> - <p>Provide a password for password authentication. If - this option is not given, the user will be asked for a - password if the password authentication method is + <p>Provides a password for password authentication. + If this option is not given, the user is asked for a + password, if the password authentication method is attempted.</p> </item> <tag><c><![CDATA[{key_cb, atom()}]]></c></tag> <item> - <p>Module implementing the behaviour <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>. + <p>Module implementing the behaviour + <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>. Can be used to customize the handling of public keys. </p> </item> <tag><c><![CDATA[{quiet_mode, atom() = boolean()}]]></c></tag> <item> - <p>If true, the client will not print out anything on authorization.</p> + <p>If <c>true</c>, the client does not print anything on authorization.</p> </item> + + <tag><c><![CDATA[{id_string, random | string()}]]></c></tag> + <item> + <p>The string that the client presents to a connected server initially. The default value is "Erlang/VSN" where VSN is the ssh application version number. + </p> + <p>The value <c>random</c> will cause a random string to be created at each connection attempt. This is to make it a bit more difficult for a malicious peer to find the ssh software brand and version. + </p> + </item> + <tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag> <item> - <p>Allow an existing file descriptor to be used - (simply passed on to the transport protocol).</p></item> + <p>Allows an existing file descriptor to be used + (by passing it on to the transport protocol).</p></item> <tag><c><![CDATA[{rekey_limit, integer()}]]></c></tag> <item> - <p>Provide, in bytes, when rekeying should be initiated, - defaults to one time each GB and one time per hour.</p> + <p>Provides, in bytes, when rekeying is to be initiated. + Defaults to once per each GB and once per hour.</p> </item> <tag><c><![CDATA[{idle_time, integer()}]]></c></tag> <item> - <p>Sets a timeout on connection when no channels are active, default is infinity</p></item> + <p>Sets a time-out on a connection when no channels are active. + Defaults to <c>infinity</c>.</p></item> </taglist> </desc> </func> <func> <name>connection_info(ConnectionRef, [Option]) ->[{Option, - Value}] </name> - <fsummary> Retrieves information about a connection. </fsummary> + Value}]</name> + <fsummary>Retrieves information about a connection.</fsummary> <type> <v>Option = client_version | server_version | user | peer | sockname </v> <v>Value = [option_value()] </v> - <v>option_value() = {{Major::integer(), Minor::integer()}, VersionString::string()} | User::string() | - Peer::{inet:hostname(), {inet::ip_adress(), inet::port_number()}} | - Sockname::{inet::ip_adress(), inet::port_number()} () </v> + <v>option_value() = {{Major::integer(), Minor::integer()}, VersionString::string()} | + User::string() | Peer::{inet:hostname(), {inet::ip_adress(), inet::port_number()}} | + Sockname::{inet::ip_adress(), inet::port_number()}</v> </type> <desc> - <p> Retrieves information about a connection. - </p> + <p>Retrieves information about a connection.</p> </desc> </func> @@ -230,111 +264,127 @@ <desc> <p>Starts a server listening for SSH connections on the given port.</p> - <p>Options are:</p> + <p>Options:</p> <taglist> <tag><c><![CDATA[{inet, inet | inet6}]]></c></tag> - <item> IP version to use when the host address is specified as <c>any</c>. </item> + <item><p>IP version to use when the host address is specified as <c>any</c>.</p></item> <tag><c><![CDATA[{subsystems, [subsystem_spec()]}]]></c></tag> <item> - Provides specifications for handling of subsystems. The - "sftp" subsystem spec can be retrieved by calling - ssh_sftpd:subsystem_spec/1. If the subsystems option is - not present the value of - <c>[ssh_sftpd:subsystem_spec([])]</c> will be used. It is - of course possible to set the option to the empty list if - you do not want the daemon to run any subsystems at all. + <p>Provides specifications for handling of subsystems. The + "sftp" subsystem specification is retrieved by calling + <c>ssh_sftpd:subsystem_spec/1</c>. If the subsystems option is + not present, the value of + <c>[ssh_sftpd:subsystem_spec([])]</c> is used. + The option can be set to the empty list if + you do not want the daemon to run any subsystems.</p> </item> <tag><c><![CDATA[{shell, {Module, Function, Args} | fun(string() = User) - > pid() | fun(string() = User, ip_address() = PeerAddr) -> pid()}]]></c></tag> <item> - Defines the read-eval-print loop used when a shell is - requested by the client. Default is to use the erlang shell: - <c><![CDATA[{shell, start, []}]]></c> + <p>Defines the read-eval-print loop used when a shell is + requested by the client. The default is to use the Erlang shell: + <c><![CDATA[{shell, start, []}]]></c></p> </item> <tag><c><![CDATA[{ssh_cli, {channel_callback(), channel_init_args()} | no_cli}]]></c></tag> <item> - Provides your own CLI implementation, i.e. a channel callback - module that implements a shell and command execution. Note - that you may customize the shell read-eval-print loop using the - option <c>shell</c> which is much less work than implementing - your own CLI channel. If set to <c>no_cli</c> you will disable - CLI channels and only subsystem channels will be allowed. + <p>Provides your own CLI implementation, that is, a channel callback + module that implements a shell and command execution. The shell + read-eval-print loop can be customized, using the + option <c>shell</c>. This means less work than implementing + an own CLI channel. If set to <c>no_cli</c>, the CLI channels + are disabled and only subsystem channels are allowed.</p> </item> <tag><c><![CDATA[{user_dir, String}]]></c></tag> <item> - <p>Sets the user directory i.e. the directory containing - ssh configuration files for the user such as + <p>Sets the user directory. That is, the directory containing + <c>ssh</c> configuration files for the user, such as <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa, - id_dsa]]></c> and + id_dsa]]></c>, and <c><![CDATA[authorized_key]]></c>. Defaults to the directory normally referred to as - <c><![CDATA[~/.ssh]]></c> </p> + <c><![CDATA[~/.ssh]]></c>.</p> </item> <tag><c><![CDATA[{system_dir, string()}]]></c></tag> <item> <p>Sets the system directory, containing the host key files - that identifies the host keys for ssh. The default is - <c><![CDATA[/etc/ssh]]></c>, note that for security reasons - this directory is normally only accessible by the root user.</p> + that identify the host keys for <c>ssh</c>. Defaults to + <c><![CDATA[/etc/ssh]]></c>. For security reasons, + this directory is normally accessible only to the root user.</p> </item> <tag><c><![CDATA[{auth_methods, string()}]]></c></tag> <item> - <p>Comma separated string that determines which - authentication methodes that the server should support and - in what order they will be tried. Defaults to + <p>Comma-separated string that determines which + authentication methods that the server is to support and + in what order they are tried. Defaults to <c><![CDATA["publickey,keyboard-interactive,password"]]></c></p> </item> <tag><c><![CDATA[{user_passwords, [{string() = User, string() = Password}]}]]></c></tag> <item> - <p>Provide passwords for password authentication.They will - be used when someone tries to connect to the server and - public key user authentication fails. The option provides - a list of valid user names and the corresponding password. + <p>Provides passwords for password authentication. The passwords + are used when someone tries to connect to the server and + public key user-authentication fails. The option provides + a list of valid usernames and the corresponding passwords. </p> </item> <tag><c><![CDATA[{password, string()}]]></c></tag> <item> - <p>Provide a global password that will authenticate any + <p>Provides a global password that authenticates any user. From a security perspective this option makes the server very vulnerable.</p> </item> <tag><c><![CDATA[{pwdfun, fun(User::string(), password::string()) -> boolean()}]]></c></tag> <item> - <p>Provide a function for password validation. This is called - with user and password as strings, and should return + <p>Provides a function for password validation. This function is called + with user and password as strings, and returns <c><![CDATA[true]]></c> if the password is valid and <c><![CDATA[false]]></c> otherwise.</p> </item> <tag><c><![CDATA[{negotiation_timeout, integer()}]]></c></tag> <item> - <p>Max time in milliseconds for the authentication negotiation. The default value is 2 minutes. If the client fails to login within this time, the connection is closed. + <p>Maximum time in milliseconds for the authentication negotiation. + Defaults to 120000 (2 minutes). If the client fails to log in within this time, + the connection is closed. </p> </item> <tag><c><![CDATA[{max_sessions, pos_integer()}]]></c></tag> <item> - <p>The maximum number of simultaneous sessions that are accepted at any time for this daemon. This includes sessions that are being authorized. So if set to <c>N</c>, and <c>N</c> clients have connected but not started the login process, the <c>N+1</c> connection attempt will be aborted. If <c>N</c> connections are authenticated and still logged in, no more loggins will be accepted until one of the existing ones log out. + <p>The maximum number of simultaneous sessions that are accepted at any time + for this daemon. This includes sessions that are being authorized. + Thus, if set to <c>N</c>, and <c>N</c> clients have connected but not started + the login process, connection attempt <c>N+1</c> is aborted. + If <c>N</c> connections are authenticated and still logged in, no more logins + are accepted until one of the existing ones log out. </p> - <p>The counter is per listening port, so if two daemons are started, one with <c>{max_sessions,N}</c> and the other with <c>{max_sessions,M}</c> there will be in total <c>N+M</c> connections accepted for the whole ssh application. + <p>The counter is per listening port. Thus, if two daemons are started, one with + <c>{max_sessions,N}</c> and the other with <c>{max_sessions,M}</c>, in total + <c>N+M</c> connections are accepted for the whole <c>ssh</c> application. </p> - <p>Note that if <c>parallel_login</c> is <c>false</c>, only one client at a time may be in the authentication phase. + <p>Notice that if <c>parallel_login</c> is <c>false</c>, only one client + at a time can be in the authentication phase. </p> - <p>As default, the option is not set. This means that the number is not limited. + <p>By default, this option is not set. This means that the number is not limited. </p> </item> <tag><c><![CDATA[{parallel_login, boolean()}]]></c></tag> <item> - <p>If set to false (the default value), only one login is handled a time. If set to true, an unlimited number of login attempts will be allowed simultanously. + <p>If set to false (the default value), only one login is handled at a time. + If set to true, an unlimited number of login attempts are allowed simultaneously. </p> - <p>If the <c>max_sessions</c> option is set to <c>N</c> and <c>parallel_login</c> is set to <c>true</c>, the max number of simultaneous login attempts at any time is limited to <c>N-K</c> where <c>K</c> is the number of authenticated connections present at this daemon. + <p>If the <c>max_sessions</c> option is set to <c>N</c> and <c>parallel_login</c> + is set to <c>true</c>, the maximum number of simultaneous login attempts at any time is + limited to <c>N-K</c>, where <c>K</c> is the number of authenticated connections present + at this daemon. </p> <warning> - <p>Do not enable <c>parallel_logins</c> without protecting the server by other means, for example the <c>max_sessions</c> option or a firewall configuration. If set to <c>true</c>, there is no protection against DOS attacks.</p> + <p>Do not enable <c>parallel_logins</c> without protecting the server by other means, + for example, by the <c>max_sessions</c> option or a firewall configuration. If set to + <c>true</c>, there is no protection against DOS attacks.</p> </warning> </item> @@ -344,27 +394,38 @@ </p> </item> + <tag><c><![CDATA[{id_string, random | string()}]]></c></tag> + <item> + <p>The string the daemon will present to a connecting peer initially. The default value is "Erlang/VSN" where VSN is the ssh application version number. + </p> + <p>The value <c>random</c> will cause a random string to be created at each connection attempt. This is to make it a bit more difficult for a malicious peer to find the ssh software brand and version. + </p> + </item> + <tag><c><![CDATA[{key_cb, atom()}]]></c></tag> <item> - <p>Module implementing the behaviour <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>. + <p>Module implementing the behaviour + <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>. Can be used to customize the handling of public keys. </p> </item> <tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag> <item> - <p>Allow an existing file-descriptor to be used - (simply passed on to the transport protocol).</p></item> - <tag><c><![CDATA[{failfun, fun(User::string(), PeerAddress::ip_address(), Reason::term()) -> _}]]></c></tag> + <p>Allows an existing file-descriptor to be used + (passed on to the transport protocol).</p></item> + <tag><c><![CDATA[{failfun, fun(User::string(), + PeerAddress::ip_address(), Reason::term()) -> _}]]></c></tag> <item> - <p>Provide a fun to implement your own logging when a user fails to authenticate.</p> + <p>Provides a fun to implement your own logging when a user fails to authenticate.</p> </item> - <tag><c><![CDATA[{connectfun, fun(User::string(), PeerAddress::ip_address(), Method::string()) ->_}]]></c></tag> + <tag><c><![CDATA[{connectfun, fun(User::string(), PeerAddress::ip_address(), + Method::string()) ->_}]]></c></tag> <item> - <p>Provide a fun to implement your own logging when a user authenticates to the server.</p> + <p>Provides a fun to implement your own logging when a user authenticates to the server.</p> </item> <tag><c><![CDATA[{disconnectfun, fun(Reason:term()) -> _}]]></c></tag> <item> - <p>Provide a fun to implement your own logging when a user disconnects from the server.</p> + <p>Provides a fun to implement your own logging when a user disconnects from the server.</p> </item> </taglist> </desc> @@ -375,16 +436,16 @@ <name>shell(Host) -> </name> <name>shell(Host, Option) -> </name> <name>shell(Host, Port, Option) -> _</name> - <fsummary> </fsummary> + <fsummary>Starts an interactive shell over an SSH server.</fsummary> <type> - <v> Host = string()</v> - <v> Port = integer()</v> - <v> Options - see ssh:connect/3</v> + <v>Host = string()</v> + <v>Port = integer()</v> + <v>Options - see ssh:connect/3</v> </type> <desc> - <p>Starts an interactive shell via an SSH server on the + <p>Starts an interactive shell over an SSH server on the given <c>Host</c>. The function waits for user input, - and will not return until the remote shell is ended (i.e. + and does not return until the remote shell is ended (that is, exit from the shell). </p> </desc> @@ -393,28 +454,29 @@ <func> <name>start() -> </name> <name>start(Type) -> ok | {error, Reason}</name> - <fsummary>Starts the SSH application. </fsummary> + <fsummary>Starts the SSH application.</fsummary> <type> <v>Type = permanent | transient | temporary</v> <v>Reason = term() </v> </type> <desc> - <p>Utility function that starts crypto, public_key and the SSH - application. Defult type is temporary. - See also <seealso marker="kernel:application">application(3)</seealso> - </p> + <p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>, + and <c>ssh</c>. Default type is <c>temporary</c>. + For more information, see the <seealso marker="kernel:application">application(3)</seealso> + manual page in <c>kernel</c>.</p> </desc> </func> <func> <name>stop() -> ok | {error, Reason}</name> - <fsummary>Stops the SSH application.</fsummary> + <fsummary>Stops the <c>ssh</c> application.</fsummary> <type> <v>Reason = term()</v> </type> <desc> - <p>Stops the SSH application. See also - <seealso marker="kernel:application">application(3)</seealso></p> + <p>Stops the <c>ssh</c> application. + For more information, see the <seealso marker="kernel:application">application(3)</seealso> + manual page in <c>kernel</c>.</p> </desc> </func> @@ -438,7 +500,7 @@ <name>stop_listener(DaemonRef) -> </name> <name>stop_listener(Address, Port) -> ok </name> <fsummary>Stops the listener, but leaves existing connections started - by the listener up and running.</fsummary> + by the listener operational.</fsummary> <type> <v>DaemonRef = ssh_daemon_ref()</v> <v>Address = ip_address()</v> @@ -446,7 +508,7 @@ </type> <desc> <p>Stops the listener, but leaves existing connections started - by the listener up and running.</p> + by the listener operational.</p> </desc> </func> diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml index a1d2402790..1dfe68b17d 100644 --- a/lib/ssh/doc/src/ssh_app.xml +++ b/lib/ssh/doc/src/ssh_app.xml @@ -18,83 +18,103 @@ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - </legalnotice> <title>SSH</title> + <prepared></prepared> + <docno></docno> + <checked></checked> + <date></date> + <rev></rev> <file>ssh_app.xml</file> </header> <app>SSH</app> - <appsummary>The ssh application implements the SSH (Secure Shell) protocol and - provides an SFTP (SSH File Transfer Protocol) client and server. </appsummary> + <appsummary>The ssh application implements the Secure Shell (SSH) protocol and + provides an SSH File Transfer Protocol (SFTP) client and server.</appsummary> + <description> + <p>The <c>ssh</c> application is an implementation of the SSH protocol in Erlang. + <c>ssh</c> offers API functions to write customized SSH clients and servers as well as + making the Erlang shell available over SSH. An SFTP client, <c>ssh_sftp</c>, and server, + <c>ssh_sftpd</c>, are also included.</p> + </description> - <section> + <section> <title>DEPENDENCIES</title> - <p>The ssh application uses the Erlang applications public_key and - crypto to handle public keys and encryption, hence these - applications needs to be loaded for the ssh application to work. In - an embedded environment that means they need to be started with - application:start/[1,2] before the ssh application is started. + <p>The <c>ssh</c> application uses the applications <c>public_key</c> and + <c>crypto</c> to handle public keys and encryption. Hence, these + applications must be loaded for the <c>ssh</c> application to work. In + an embedded environment this means that they must be started with + <c>application:start/[1,2]</c> before the <c>ssh</c> application is started. </p> </section> <section> <title>CONFIGURATION</title> - <p>The ssh application does not currently have an application - specific configuration file as described in application(3), - however it will by default use the following configuration files - from openssh: known_hosts, authorized_keys, authorized_keys2, - id_dsa and id_rsa, ssh_host_dsa_key and ssh_host_rsa_key. By - default Erlang SSH will look for id_dsa, id_rsa, known_hosts - and authorized_keys in ~/.ssh, and the host key files in /etc/ssh - . These locations may be changed by the options user_dir and - system_dir. Public key handling may also be customized by - providing a callback module implementing the behaviors - <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and - <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>. - </p> + <p>The <c>ssh</c> application does not have an application- + specific configuration file, as described in <seealso marker="kernel:application">application(3)</seealso>. + However, by default it use the following configuration files + from OpenSSH:</p> + <list type="bulleted"> + <item><c>known_hosts</c></item> + <item><c>authorized_keys</c></item> + <item><c>authorized_keys2</c></item> + <item><c>id_dsa</c></item> + <item><c>id_rsa</c></item> + <item><c>ssh_host_dsa_key</c></item> + <item><c>ssh_host_rsa_key</c></item> + </list> + <p>By default, <c>ssh</c> looks for <c>id_dsa</c>, <c>id_rsa</c>, + <c>known_hosts</c>, and <c>authorized_keys</c> in ~/.ssh, + and for the host key files in <c>/etc/ssh</c>. These locations can be changed + by the options <c>user_dir</c> and <c>system_dir</c>. + </p> + <p>Public key handling can also be customized through a callback module that + implements the behaviors + <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and + <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>. + </p> - <section> - <title>PUBLIC KEYS</title> - <p> - id_dsa and id_rsa are the users private key files, note that - the public key is part of the private key so the ssh - application will not use the id_<*>.pub files. These are - for the users convenience when he/she needs to convey their + </section> + <section> + <title>Public Keys</title> + <p><c>id_dsa</c> and <c>id_rsa</c> are the users private key files. + Notice that the public key is part of the private key so the <c>ssh</c> + application does not use the <c>id_<*>.pub</c> files. These are + for the user's convenience when it is needed to convey the user's public key. </p> - </section> - - <section> - <title>KNOW HOSTS</title> - <p>The known_hosts file contains a list of approved servers and - their public keys. Once a server is listed, it can be verified + </section> + <section> + <title>Known Hosts</title> + <p>The <c>known_hosts</c> file contains a list of approved servers and + their public keys. Once a server is listed, it can be verified without user interaction. </p> - </section> - - <section> - <title>AUTHORIZED KEYS</title> - <p>The authorized key file keeps track of the user's authorized + </section> + <section> + <title>Authorized Keys</title> + <p>The <c>authorized_key</c> file keeps track of the user's authorized public keys. The most common use of this file is to let users - log in without entering their password which is supported by the - Erlang SSH daemon. + log in without entering their password, which is supported by the + Erlang <c>ssh</c> daemon. </p> - </section> - - <section> - <title>HOST KEYS</title> - <p>Currently rsa and dsa host keys are supported and are - expected to be found in files named ssh_host_rsa_key and - ssh_host_dsa_key. + </section> + <section> + <title>Host Keys</title> + <p>RSA and DSA host keys are supported and are + expected to be found in files named <c>ssh_host_rsa_key</c> and + <c>ssh_host_dsa_key</c>. </p> - </section> + </section> + <section> + <title>ERROR LOGGER AND EVENT HANDLERS</title> + <p>The <c>ssh</c> application uses the default <seealso marker="kernel:error_logger">OTP error logger</seealso> to log unexpected errors or print information about special events.</p> </section> <section> <title>SEE ALSO</title> - <p>application(3)</p> + <p><seealso marker="kernel:application">application(3)</seealso></p> </section> </appref> diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml index a52a6a115e..429ef3c849 100644 --- a/lib/ssh/doc/src/ssh_channel.xml +++ b/lib/ssh/doc/src/ssh_channel.xml @@ -23,69 +23,84 @@ The Initial Developer of the Original Code is Ericsson AB. </legalnotice> <title>ssh_channel</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> </header> <module>ssh_channel</module> <modulesummary>-behaviour(ssh_channel). </modulesummary> <description> <p>SSH services (clients and servers) are implemented as channels - that are multiplexed over an SSH connection and communicates via + that are multiplexed over an SSH connection and communicates over the <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH Connection Protocol</url>. This module provides a callback API - that takes care of generic channel aspects such as flow control - and close messages and lets the callback functions take care of + that takes care of generic channel aspects, such as flow control + and close messages. It lets the callback functions take care of the service (application) specific parts. This behavior also ensures that the channel process honors the principal of an OTP-process so that it can be part of a supervisor tree. This is a requirement of channel processes implementing a subsystem that will be added to - the SSH applications supervisor tree. + the <c>ssh</c> applications supervisor tree. </p> - <note> <p>When implementing a SSH subsystem use the - <c>-behaviour(ssh_daemon_channel).</c> instead of <c>-behaviour(ssh_channel).</c> - as the only relevant callback functions for subsystems are - init/1, handle_ssh_msg/2, handle_msg/2 and terminate/2, so the ssh_daemon_channel - behaviour is limited version of the ssh_channel behaviour. - </p> </note> + <note><p>When implementing an <c>ssh</c> subsystem, use + <c>-behaviour(ssh_daemon_channel)</c> instead of <c>-behaviour(ssh_channel)</c>. + The reason is that the only relevant callback functions for subsystems are + <c>init/1</c>, <c>handle_ssh_msg/2</c>, <c>handle_msg/2</c>, and <c>terminate/2</c>. + So, the <c>ssh_daemon_channel</c> behaviour is a limited version of the + <c>ssh_channel</c> behaviour. + </p></note> </description> <section> - <title>DATA TYPES </title> + <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module - and/or abstractions to indicate the intended use of the data - type:</p> + <p>Type definitions that are used more than once in this module, + or abstractions to indicate the intended use of the data + type, or both:</p> - <p><c>boolean() = true | false </c></p> - <p><c>string() = list of ASCII characters</c></p> - <p><c>timeout() = infinity | integer() - in milliseconds.</c></p> - <p><c>ssh_connection_ref() - opaque to the user returned by - ssh:connect/3 or sent to an SSH channel process</c></p> - <p><c>ssh_channel_id() = integer() </c></p> - <p><c>ssh_data_type_code() = 1 ("stderr") | 0 ("normal") are - currently valid values see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 5.2.</c></p> + <taglist> + <tag><c>boolean()</c></tag> + <item><p>= <c>true | false</c></p></item> + <tag><c>string()</c></tag> + <item><p>= list of ASCII characters</p></item> + <tag><c>timeout()</c></tag> + <item><p>= <c>infinity | integer()</c> in milliseconds</p></item> + <tag><c>ssh_connection_ref()</c></tag> + <item><p>Opaque to the user, returned by + <c>ssh:connect/3</c> or sent to an SSH channel process</p></item> + <tag><c>ssh_channel_id()</c></tag> + <item><p>= <c>integer()</c></p></item> + <tag><c>ssh_data_type_code()</c></tag> + <item><p>= <c>1</c> ("stderr") | <c>0</c> ("normal") are + the valid values, + see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> + Section 5.2</p></item> + </taglist> </section> <funcs> <func> <name>call(ChannelRef, Msg) -></name> <name>call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name> - <fsummary> Makes a synchronous call to a channel.</fsummary> + <fsummary>Makes a synchronous call to a channel.</fsummary> <type> <v>ChannelRef = pid() </v> - <d>As returned by start_link/4 </d> - <v>Msg = term() </v> - <v>Timeout = timeout() </v> - <v>Reply = term() </v> - <v>Reason = closed | timeout </v> + <d>As returned by <c>start_link/4</c></d> + <v>Msg = term()</v> + <v>Timeout = timeout()</v> + <v>Reply = term()</v> + <v>Reason = closed | timeout</v> </type> <desc> <p>Makes a synchronous call to the channel process by sending - a message and waiting until a reply arrives or a timeout - occurs. The channel will call <seealso marker = + a message and waiting until a reply arrives, or a time-out + occurs. The channel calls <seealso marker = "#Module:handle_call-3">Module:handle_call/3</seealso> - to handle the message. If the channel process does not exist + to handle the message. If the channel process does not exist, <c>{error, closed}</c> is returned. </p> </desc> @@ -96,14 +111,14 @@ <fsummary>Sends an asynchronous message to the channel ChannelRef and returns ok.</fsummary> <type> - <v>ChannelRef = pid() </v> - <d>As returned by start_link/4 </d> - <v>Msg = term() </v> + <v>ChannelRef = pid()</v> + <d>As returned by <c>start_link/4</c></d> + <v>Msg = term()</v> </type> <desc> <p>Sends an asynchronous message to the channel process and returns ok immediately, ignoring if the destination node or - channel process does not exist. The channel will call + channel process does not exist. The channel calls <seealso marker = "#Module:handle_cast-2">Module:handle_cast/2</seealso> to handle the message. </p> @@ -112,31 +127,32 @@ <func> <name>enter_loop(State) -> _ </name> - <fsummary> Makes an existing process an ssh_channel process. </fsummary> + <fsummary>Makes an existing process an ssh_channel process.</fsummary> <type> - <v> State = term() - as returned by <seealso marker = "#init-1">ssh_channel:init/1</seealso></v> + <v>State = term() - as returned by + <seealso marker = "#init-1">ssh_channel:init/1</seealso></v> </type> <desc> - <p> Makes an existing process an <c>ssh_channel</c> - process. Does not return, instead the calling process will - enter the <c>ssh_channel</c> process receive loop and become an - <c>ssh_channel process.</c> The process must have been started using - one of the start functions in proc_lib, see <seealso - marker="stdlib:proc_lib">proc_lib(3)</seealso>. The - user is responsible for any initialization of the process - and needs to call <seealso marker = "#init-1">ssh_channel:init/1</seealso> + <p>Makes an existing process an <c>ssh_channel</c> + process. Does not return, instead the calling process + enters the <c>ssh_channel</c> process receive loop and become an + <c>ssh_channel process</c>. The process must have been started using + one of the start functions in <c>proc_lib</c>, see the <seealso + marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in <c>stdlib</c>. + The user is responsible for any initialization of the process + and must call <seealso marker = "#init-1">ssh_channel:init/1</seealso>. </p> </desc> </func> <func> <name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name> - <fsummary> Initiates a ssh_channel process.</fsummary> + <fsummary>Initiates an <c>ssh_channel</c> process.</fsummary> <type> <v>Options = [{Option, Value}]</v> <v>State = term()</v> - <v>Timeout = timeout() </v> - <v>Reason = term() </v> + <v>Timeout = timeout()</v> + <v>Reason = term()</v> </type> <desc> <p> @@ -144,48 +160,47 @@ </p> <taglist> <tag><c><![CDATA[{channel_cb, atom()}]]></c></tag> - <item>The module that implements the channel behaviour.</item> + <item><p>The module that implements the channel behaviour.</p></item> <tag><c><![CDATA[{init_args(), list()}]]></c></tag> - <item> The list of arguments to the callback module's - init function.</item> + <item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item> <tag><c><![CDATA[{cm, connection_ref()}]]></c></tag> - <item> Reference to the ssh connection as returned by <seealso - marker="ssh#connect-3">ssh:connect/3</seealso></item> + <item><p>Reference to the <c>ssh</c> connection as returned by <seealso + marker="ssh#connect-3">ssh:connect/3</seealso></p></item> <tag><c><![CDATA[{channel_id, channel_id()}]]></c></tag> - <item> Id of the SSH channel.</item> + <item><p>Id of the <c>ssh</c> channel.</p></item> </taglist> <note><p>This function is normally not called by the - user. The user only needs to call if for some reason the + user. The user only needs to call if the channel process needs to be started with help of <c>proc_lib</c> instead of calling <c>ssh_channel:start/4</c> or - <c>ssh_channel:start_link/4</c> </p> + <c>ssh_channel:start_link/4</c>.</p> </note> </desc> </func> <func> <name>reply(Client, Reply) -> _</name> - <fsummary>Send a reply to a client.</fsummary> + <fsummary>Sends a reply to a client.</fsummary> <type> <v>Client - opaque to the user, see explanation below</v> <v>Reply = term()</v> </type> <desc> - <p>This function can be used by a channel to explicitly send a + <p>This function can be used by a channel to send a reply to a client that called <c>call/[2,3]</c> when the reply cannot be defined in the return value of <seealso marker ="#Module:handle_call-3">Module:handle_call/3</seealso>.</p> <p><c>Client</c> must be the <c>From</c> argument provided to the callback function <c>handle_call/3</c>. <c>Reply</c> is an arbitrary term, - which will be given back to the client as the return value of - <seealso marker="#call-2">ssh_channel:call/[2,3].</seealso>></p> + which is given back to the client as the return value of + <seealso marker="#call-2">ssh_channel:call/[2,3].</seealso></p> </desc> </func> @@ -193,24 +208,25 @@ <name>start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name> <name>start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> {ok, ChannelRef} | {error, Reason}</name> - <fsummary> Starts a processes that handles a SSH channel. </fsummary> + <fsummary>Starts a process that handles an SSH channel.</fsummary> <type> <v>SshConnection = ssh_connection_ref()</v> - <v>ChannelId = ssh_channel_id() </v> - <d> As returned by cannot be defined in the return value of - <seealso marker ="ssh_connection#session_channel/2">ssh_connection:session_channel/[2,4]</seealso></d> + <v>ChannelId = ssh_channel_id()</v> + <d>As returned by + <seealso marker ="ssh_connection#session_channel/2"> + ssh_connection:session_channel/[2,4]</seealso>.</d> <v>ChannelCb = atom()</v> - <d> The name of the module implementing the service specific parts + <d>Name of the module implementing the service-specific parts of the channel.</d> <v>CbInitArgs = [term()]</v> - <d>Argument list for the init function in the callback module. </d> + <d>Argument list for the <c>init</c> function in the callback module.</d> <v>ChannelRef = pid()</v> </type> <desc> - <p>Starts a processes that handles an SSH channel. It will be - called internally by the SSH daemon or explicitly by the SSH - client implementations. The behavior will set the - <c>trap_exit</c> flag to true. + <p>Starts a process that handles an SSH channel. It is + called internally, by the <c>ssh</c> daemon, or explicitly by the <c>ssh</c> + client implementations. The behavior sets the + <c>trap_exit</c> flag to <c>true</c>. </p> </desc> </func> @@ -219,19 +235,19 @@ <section> <marker id="cb_timeouts"></marker> - <title> CALLBACK TIMEOUTS</title> + <title>CALLBACK TIME-OUTS</title> - <p>The timeout values that may be returned by the callback functions - has the same semantics as in a <seealso marker="stdlib:gen_server">gen_server</seealso> - If the timeout occurs <seealso marker="#Module:handle_msg-2">handle_msg/2</seealso> - will be called as <c>handle_msg(timeout, State). </c></p> + <p>The time-out values that can be returned by the callback functions + have the same semantics as in a <seealso marker="stdlib:gen_server">gen_server</seealso>. + If the time-out occurs, <seealso marker="#Module:handle_msg-2">handle_msg/2</seealso> + is called as <c>handle_msg(timeout, State)</c>.</p> </section> <funcs> <func> <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name> - <fsummary> Converts process state when code is changed.</fsummary> + <fsummary>Converts process state when code is changed.</fsummary> <type> <v>OldVsn = term()</v> <d>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and @@ -241,31 +257,31 @@ <c>Module</c>. If no such attribute is defined, the version is the checksum of the BEAM file.</d> <v>State = term()</v> - <d>The internal state of the channel.</d> + <d>Internal state of the channel.</d> <v>Extra = term()</v> - <d>Passed as-is from the <c>{advanced,Extra}</c> + <d>Passed “as-is” from the <c>{advanced,Extra}</c> part of the update instruction.</d> </type> <desc> - <p> Converts process state when code is changed.</p> + <p>Converts process state when code is changed.</p> - <p>This function is called by a client side channel when it - should update its internal state during a release - upgrade/downgrade, i.e. when the instruction - <c>{update,Module,Change,...}</c> where - <c>Change={advanced,Extra}</c> is given in the <c>appup</c> - file. See <seealso marker="doc/design_principles:release_handling#instr">OTP - Design Principles</seealso> for more information. + <p>This function is called by a client-side channel when it + is to update its internal state during a release + upgrade or downgrade, that is, when the instruction + <c>{update,Module,Change,...}</c>, where + <c>Change={advanced,Extra}</c>, is given in the <c>appup</c> + file. For more information, refer to Section 9.11.6 + Release Handling Instructions in the + <seealso marker="doc/design_principles:release_handling#instr">System Documentation</seealso>. </p> <note><p>Soft upgrade according to the OTP release concept is not straight forward for the server side, as subsystem - channel processes are spawned by the SSH application and - hence added to its supervisor tree. It could be possible to - upgrade the subsystem channels, when upgrading the user - application, if the callback functions can handle two - versions of the state, but this function can not be used in - the normal way.</p> + channel processes are spawned by the <c>ssh</c> application and + hence added to its supervisor tree. The subsystem channels can + be upgraded when upgrading the user application, if the callback + functions can handle two versions of the state, but this function + cannot be used in the normal way.</p> </note> </desc> @@ -274,30 +290,30 @@ <func> <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} | {stop, Reason}</name> - <fsummary> Makes necessary initializations and returns the + <fsummary>Makes necessary initializations and returns the initial channel state if the initializations succeed.</fsummary> <type> - <v> Args = term() </v> - <d> Last argument to ssh_channel:start_link/4.</d> - <v> State = term() </v> - <v> Reason = term() </v> + <v>Args = term()</v> + <d>Last argument to <c>ssh_channel:start_link/4</c>.</d> + <v>State = term()</v> + <v>Reason = term()</v> </type> <desc> - <p> Makes necessary initializations and returns the initial channel + <p>Makes necessary initializations and returns the initial channel state if the initializations succeed. </p> - <p>For more detailed information on timeouts see the section - <seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p> + <p>For more detailed information on time-outs, see Section + <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>. </p> </desc> </func> <func> <name>Module:handle_call(Msg, From, State) -> Result</name> - <fsummary> Handles messages sent by calling - <c>ssh_channel:call/[2,3]</c></fsummary> + <fsummary>Handles messages sent by calling + <c>ssh_channel:call/[2,3]</c>.</fsummary> <type> <v>Msg = term()</v> - <v>From = opaque to the user should be used as argument to + <v>From = Opaque to the user, is to be used as argument to ssh_channel:reply/2</v> <v>State = term()</v> <v>Result = {reply, Reply, NewState} | {reply, Reply, NewState, timeout()} @@ -311,15 +327,15 @@ <p>Handles messages sent by calling <seealso marker="#call-2">ssh_channel:call/[2,3]</seealso> </p> - <p>For more detailed information on timeouts see the section - <seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p> + <p>For more detailed information on time-outs,, see Section + <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>.</p> </desc> </func> <func> <name>Module:handle_cast(Msg, State) -> Result</name> - <fsummary> Handles messages sent by calling - <c>ssh_channel:cact/2</c></fsummary> + <fsummary>Handles messages sent by calling + <c>ssh_channel:cact/2</c>.</fsummary> <type> <v>Msg = term()</v> <v>State = term()</v> @@ -329,11 +345,11 @@ <v>Reason = term()</v> </type> <desc> - <p> Handles messages sent by calling - <c>ssh_channel:cast/2</c> + <p>Handles messages sent by calling + <c>ssh_channel:cast/2</c>. </p> - <p>For more detailed information on timeouts see the section - <seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p> + <p>For more detailed information on time-outs, see Section + <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>.</p> </desc> </func> @@ -341,33 +357,33 @@ <name>Module:handle_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> - <fsummary> Handle other messages than SSH connection protocol, - call or cast messages sent to the channel.</fsummary> + <fsummary>Handles other messages than SSH connection protocol, + call, or cast messages sent to the channel.</fsummary> <type> <v>Msg = timeout | term()</v> <v>ChannelId = ssh_channel_id()</v> <v>State = term() </v> </type> <desc> - <p>Handle other messages than ssh connection protocol, call or + <p>Handles other messages than SSH Connection Protocol, call, or cast messages sent to the channel. </p> - <p> Possible erlang 'EXIT'-messages should be handled by this - function and all channels should handle the following message.</p> + <p>Possible Erlang 'EXIT' messages is to be handled by this + function and all channels are to handle the following message.</p> <taglist> <tag><c><![CDATA[{ssh_channel_up, ssh_channel_id(), ssh_connection_ref()}]]></c></tag> - <item>This is the first messages that will be received by - the channel, it is sent just before the <seealso + <item><p>This is the first message that the channel receives. + It is sent just before the <seealso marker="#init-1">ssh_channel:init/1</seealso> function - returns successfully. This is especially useful if the + returns successfully. This is especially useful if the server wants to send a message to the client without first receiving a message from it. If the message is not - useful for your particular scenario just ignore it by - immediately returning {ok, State}. - </item> + useful for your particular scenario, ignore it by + immediately returning <c>{ok, State}</c>. + </p></item> </taglist> </desc> </func> @@ -375,42 +391,44 @@ <func> <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> - <fsummary> Handles ssh connection protocol messages. </fsummary> + <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = <seealso marker="ssh_connection"> ssh_connection:event() </seealso> </v> + <v>Msg = ssh_connection:event()</v> <v>ChannelId = ssh_channel_id()</v> <v>State = term()</v> </type> <desc> - <p> Handles SSH connection protocol messages that may need - service specific attention. + <p>Handles SSH Connection Protocol messages that may need + service-specific attention. For details, + see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. </p> - <p> The following message is completely taken care of by the - SSH channel behavior</p> + <p>The following message is taken care of by the + <c>ssh_channel</c> behavior.</p> <taglist> <tag><c><![CDATA[{closed, ssh_channel_id()}]]></c></tag> - <item> The channel behavior will send a close message to the - other side if such a message has not already been sent and - then terminate the channel with reason normal.</item> + <item><p>The channel behavior sends a close message to the + other side, if such a message has not already been sent. + Then it terminates the channel with reason <c>normal</c>.</p></item> </taglist> </desc> </func> <func> <name>Module:terminate(Reason, State) -> _</name> - <fsummary> </fsummary> + <fsummary>Does cleaning up before channel process termination. +</fsummary> <type> <v>Reason = term()</v> <v>State = term()</v> </type> <desc> <p>This function is called by a channel process when it is - about to terminate. Before this function is called <seealso + about to terminate. Before this function is called, <seealso marker="ssh_connection#close-2"> ssh_connection:close/2 - </seealso> will be called if it has not been called earlier. - This function should do any necessary cleaning + </seealso> is called, if it has not been called earlier. + This function does any necessary cleaning up. When it returns, the channel process terminates with reason <c>Reason</c>. The return value is ignored. </p> diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml index f3d05a8980..a8dda042c9 100644 --- a/lib/ssh/doc/src/ssh_client_key_api.xml +++ b/lib/ssh/doc/src/ssh_client_key_api.xml @@ -23,102 +23,112 @@ The Initial Developer of the Original Code is Ericsson AB. </legalnotice> <title>ssh_client_key_api</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> </header> <module>ssh_client_key_api</module> <modulesummary> -behaviour(ssh_client_key_api). </modulesummary> <description> - <p> Behavior describing the API for an SSH client's public key handling. - By implementing the callbacks defined. - in this behavior it is possible to customize the SSH client's public key - handling. By default the SSH application implements this behavior - with help of the standard openssh files, see <seealso marker="SSH_app"> ssh(6)</seealso>. </p> + <p>Behavior describing the API for public key handling of an SSH client. By implementing + the callbacks defined in this behavior, the public key handling of an SSH client can + be customized. By default the <c>ssh</c> application implements this behavior + with help of the standard OpenSSH files, + see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p> </description> <section> - <title>DATA TYPES </title> + <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module - and/or abstractions to indicate the intended use of the data - type. For more details on public key data types - see the <seealso marker="public_key:public_key_records"> public_key user's guide.</seealso> + <p>Type definitions that are used more than once in this module, + or abstractions to indicate the intended use of the data + type, or both. For more details on public key data types, + refer to Section 2 Public Key Records in the + <seealso marker="public_key:public_key_records"> public_key user's guide:</seealso> </p> - - <p> boolean() = true | false</p> - <p> string() = [byte()] </p> - <p> public_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p> - <p> private_key() = #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</p> - <p> public_key_algorithm() = 'ssh-rsa'| 'ssh-dss' | atom()</p> - + <taglist> + <tag><c>boolean()</c></tag> + <item><p>= <c>true | false</c></p></item> + <tag><c>string()</c></tag> + <item><p>= <c>[byte()]</c></p></item> + <tag><c>public_key()</c></tag> + <item><p>= <c>#'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</c></p></item> + <tag><c>private_key()</c></tag> + <item><p>= <c>#'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</c></p></item> + <tag><c>public_key_algorithm()</c></tag> + <item><p>= <c>'ssh-rsa'| 'ssh-dss' | atom()</c></p></item> + </taglist> </section> <funcs> <func> <name>Module:add_host_key(HostNames, Key, ConnectOptions) -> ok | {error, Reason}</name> - <fsummary>Adds a host key to the set of trusted host keys</fsummary> + <fsummary>Adds a host key to the set of trusted host keys.</fsummary> <type> <v>HostNames = string()</v> - <d>Description of the host that owns the <c>PublicKey</c></d> + <d>Description of the host that owns the <c>PublicKey</c>.</d> - <v>Key = public_key() </v> - <d> Normally an RSA or DSA public key but handling of other public keys can be added</d> + <v>Key = public_key()</v> + <d>Normally an RSA or DSA public key, but handling of other public keys can be added.</d> - <v>ConnectOptions = proplists:proplist() </v> - <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d> - <v>Reason = term() </v> + <v>ConnectOptions = proplists:proplist()</v> + <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d> + <v>Reason = term().</v> </type> <desc> - <p> Adds a host key to the set of trusted host keys</p> + <p>Adds a host key to the set of trusted host keys.</p> </desc> </func> <func> <name>Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name> - <fsummary>Checks if a host key is trusted</fsummary> + <fsummary>Checks if a host key is trusted.</fsummary> <type> <v>Key = public_key() </v> - <d> Normally an RSA or DSA public key but handling of other public keys can be added</d> + <d>Normally an RSA or DSA public key, but handling of other public keys can be added.</d> <v>Host = string()</v> - <d>Description of the host</d> + <d>Description of the host.</d> <v>Algorithm = public_key_algorithm()</v> - <d> Host key algorithm. Should support 'ssh-rsa'| 'ssh-dss' but additional algorithms + <d>Host key algorithm. Is to support <c>'ssh-rsa'| 'ssh-dss'</c>, but more algorithms can be handled.</d> - <v> ConnectOptions = proplists:proplist() </v> - <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d> + <v>ConnectOptions = proplists:proplist() </v> + <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>.</d> - <v> Result = boolean()</v> + <v>Result = boolean()</v> </type> <desc> - <p>Checks if a host key is trusted</p> + <p>Checks if a host key is trusted.</p> </desc> </func> <func> <name>Module:user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name> - <fsummary>Fetches the users "public key" matching the <c>Algorithm</c>.</fsummary> + <fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary> <type> <v>Algorithm = public_key_algorithm()</v> - <d> Host key algorithm. Should support 'ssh-rsa'| 'ssh-dss' but additional algorithms + <d>Host key algorithm. Is to support <c>'ssh-rsa'| 'ssh-dss'</c> but more algorithms can be handled.</d> - <v> ConnectOptions = proplists:proplist() </v> - <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d> + <v>ConnectOptions = proplists:proplist()</v> + <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d> - <v> PrivateKey = private_key()</v> - <d> The private key of the user matching the <c>Algorithm</c></d> + <v>PrivateKey = private_key()</v> + <d>Private key of the user matching the <c>Algorithm</c>.</d> - <v>Reason = term() </v> + <v>Reason = term()</v> </type> <desc> - <p>Fetches the users "public key" matching the <c>Algorithm</c>. - <note><p>The private key contains the public key</p></note> - </p> + <p>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</p> + <note><p>The private key contains the public key.</p></note> + </desc> </func> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 5e2926dfa6..669a361db9 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -24,156 +24,174 @@ </legalnotice> <title>ssh_connection</title> + <prepared></prepared> + <docno></docno> <date></date> + <rev></rev> </header> <module>ssh_connection</module> - <modulesummary>This module provides API functions to send <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH Connection Protocol </url> + <modulesummary>This module provides API functions to send + <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH Connection Protocol </url> events to the other side of an SSH channel. </modulesummary> <description> - <p>The SSH Connection Protocol is used by clients and servers - (i.e. SSH channels) to communicate over the SSH connection. The - API functions in this module sends SSH Connection Protocol events - that are received as messages by the remote channel. - In the case that the receiving channel is an Erlang process the - message will be on the following format - <c><![CDATA[{ssh_cm, ssh_connection_ref(), ssh_event_msg()}]]></c>. If the <seealso - marker="ssh_channel">ssh_channel</seealso> behavior is used to - implement the channel process these will be handled by - <seealso - marker="ssh_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2 </seealso>.</p> + <p>The SSH Connection Protocol is used by clients and servers, + that is, SSH channels, to communicate over the SSH connection. The + API functions in this module send SSH Connection Protocol events, + which are received as messages by the remote channel. + If the receiving channel is an Erlang process, the + messages have the format + <c><![CDATA[{ssh_cm, ssh_connection_ref(), ssh_event_msg()}]]></c>. + If the <seealso marker="ssh_channel">ssh_channel</seealso> behavior is used to + implement the channel process, these messages are handled by + <seealso marker="ssh_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</seealso>.</p> </description> <section> - <title>DATA TYPES </title> - - <p>Type definitions that are used more than once in this module and/or - abstractions to indicate the intended use of the data type:</p> - - <p><c>boolean() = true | false </c></p> - <p><c>string() = list of ASCII characters</c></p> - <p><c>timeout() = infinity | integer() - in milliseconds.</c></p> - <p><c>ssh_connection_ref() - opaque to the user returned by - ssh:connect/3 or sent to an SSH channel processes</c></p> - <p><c>ssh_channel_id() = integer() </c></p> - <p><c>ssh_data_type_code() = 1 ("stderr") | 0 ("normal") are - currently valid values see</c> <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 5.2.</p> - <p><c>ssh_request_status() = success | failure</c></p> - <p><c>event() = {ssh_cm, ssh_connection_ref(), ssh_event_msg()} </c></p> - <p><c>ssh_event_msg() = data_events() | status_events() | terminal_events() </c></p> - <p><c>reason() = timeout | closed </c></p> + <title>DATA TYPES</title> + + <p>Type definitions that are used more than once in this module, + or abstractions to indicate the intended use of the data + type, or both:</p> + + <taglist> + <tag><c>boolean()</c></tag> + <item><p>= <c>true | false </c></p></item> + <tag><c>string()</c></tag> + <item><p>= list of ASCII characters</p></item> + <tag><c>timeout()</c></tag> + <item><p>= <c>infinity | integer()</c> in milliseconds</p></item> + <tag><c>ssh_connection_ref()</c></tag> + <item><p>Opaque to the user, returned by + <c>ssh:connect/3</c> or sent to an SSH channel processes</p></item> + <tag><c>ssh_channel_id()</c></tag> + <item><p>= <c>integer()</c></p></item> + <tag><c>ssh_data_type_code()</c></tag> + <item><p>= <c>1</c> ("stderr") | <c>0</c> ("normal") are + valid values, see + <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 5.2.</p></item> + <tag><c>ssh_request_status() ssh_request_status()</c></tag> + <item><p>= <c>success | failure</c></p></item> + <tag><c>event()</c></tag> + <item><p>= <c>{ssh_cm, ssh_connection_ref(), ssh_event_msg()}</c></p></item> + <tag><c>ssh_event_msg()</c></tag> + <item><p>= <c>data_events() | status_events() | terminal_events()</c></p></item> + <tag><c>reason()</c></tag> + <item><p>= <c>timeout | closed</c></p></item> + </taglist> <taglist> - <tag><b>data_events()</b></tag> + <tag><em>data_events()</em></tag> <item> <taglist> <tag><c><![CDATA[{data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}]]></c></tag> - <item> Data has arrived on the channel. This event is sent as - result of calling <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5] </seealso></item> + <item><p>Data has arrived on the channel. This event is sent as a + result of calling <seealso marker="ssh_connection#send-3"> + ssh_connection:send/[3,4,5]</seealso>.</p></item> <tag><c><![CDATA[{eof, ssh_channel_id()}]]></c></tag> - <item>Indicates that the other side will not send any more - data. This event is sent as result of calling <seealso - marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso> - </item> + <item><p>Indicates that the other side sends no more data. + This event is sent as a result of calling <seealso + marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. + </p></item> </taglist> </item> - <tag><b>status_events()</b></tag> + <tag><em>status_events()</em></tag> <item> <taglist> <tag><c><![CDATA[{signal, ssh_channel_id(), ssh_signal()}]]></c></tag> - <item>A signal can be delivered to the remote process/service - using the following message. Some systems will not support - signals, in which case they should ignore this message. There is - currently no funtion to generate this event as the signals - refered to are on OS-level and not something generated by an - Erlang program.</item> + <item><p>A signal can be delivered to the remote process/service + using the following message. Some systems do not support + signals, in which case they are to ignore this message. There is + currently no function to generate this event as the signals + referred to are on OS-level and not something generated by an + Erlang program.</p></item> <tag><c><![CDATA[{exit_signal, ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg, string() = LanguageString}]]></c></tag> - <item>A remote execution may terminate violently due to a signal - then this message may be received. For details on valid string - values see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> section 6.10. Special case of the signals - mentioned above.</item> + <item><p>A remote execution can terminate violently because of a signal. + Then this message can be received. For details on valid string + values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> + Section 6.10, which shows a special case of these signals.</p></item> <tag><c><![CDATA[{exit_status, ssh_channel_id(), integer() = ExitStatus}]]></c></tag> - <item> When the command running at the other end terminates, the + <item><p>When the command running at the other end terminates, the following message can be sent to return the exit status of the - command. A zero 'exit_status' usually means that the command - terminated successfully. This event is sent as result of calling + command. A zero <c>exit_status</c> usually means that the command + terminated successfully. This event is sent as a result of calling <seealso marker="ssh_connection#exit_status-3"> - ssh_connection:exit_status/3</seealso></item> + ssh_connection:exit_status/3</seealso>.</p></item> <tag><c><![CDATA[{closed, ssh_channel_id()}]]></c></tag> - <item> This event is sent as result of calling - <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso> Both the handling of this - event and sending of it will be taken care of by the - <seealso marker="ssh_channel">ssh_channel</seealso> behavior.</item> + <item><p>This event is sent as a result of calling + <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. + Both the handling of this event and sending it are taken care of by the + <seealso marker="ssh_channel">ssh_channel</seealso> behavior.</p></item> </taglist> </item> - <tag><b>terminal_events()</b></tag> + <tag><em>terminal_events()</em></tag> <item> - <p> Channels implementing a shell and command execution on the - server side should handle the following messages that may be sent by client channel processes. </p> + <p>Channels implementing a shell and command execution on the + server side are to handle the following messages that can be sent by client- + channel processes.</p> - <note> <p>Events that includes a <c> WantReply</c> expects the event handling - process to call <seealso marker="ssh_connection#reply_request-4">ssh_connection:reply_request/4</seealso> - with the boolean value of <c> WantReply</c> as the second - argument. </p></note> + <p>Events that include a <c>WantReply</c> expect the event handling + process to call <seealso marker="ssh_connection#reply_request-4"> + ssh_connection:reply_request/4</seealso> + with the boolean value of <c>WantReply</c> as the second argument.</p> <taglist> <tag><c><![CDATA[{env, ssh_channel_id(), boolean() = WantReply, string() = Var, string() = Value}]]></c></tag> - <item> Environment variables may be passed to the shell/command - to be started later. This event is sent as result of calling <seealso - marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso> - </item> + <item><p>Environment variables can be passed to the shell/command + to be started later. This event is sent as a result of calling <seealso + marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. + </p></item> <tag><c><![CDATA[{pty, ssh_channel_id(), boolean() = WantReply, {string() = Terminal, integer() = CharWidth, integer() = RowHeight, integer() = PixelWidth, integer() = PixelHeight, [{atom() | integer() = Opcode, integer() = Value}] = TerminalModes}}]]></c></tag> - <item>A pseudo-terminal has been requested for the - session. Terminal is the value of the TERM environment - variable value (e.g., vt100). Zero dimension parameters must - be ignored. The character/row dimensions override the pixel - dimensions (when nonzero). Pixel dimensions refer to the - drawable area of the window. The <c>Opcode</c> in the + <item><p>A pseudo-terminal has been requested for the + session. <c>Terminal</c> is the value of the TERM environment + variable value, that is, <c>vt100</c>. Zero dimension parameters must + be ignored. The character/row dimensions override the pixel + dimensions (when non-zero). Pixel dimensions refer to the + drawable area of the window. <c>Opcode</c> in the <c>TerminalModes</c> list is the mnemonic name, represented - as an lowercase erlang atom, defined in - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 8. - It may also be an opcode if the mnemonic name is not listed in the - RFC. Example <c>OP code: 53, mnemonic name ECHO erlang atom: - echo</c>.This event is sent as result of calling <seealso - marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso></item> + as a lowercase Erlang atom, defined in + <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>, Section 8. + It can also be an <c>Opcode</c> if the mnemonic name is not listed in the + RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom: + echo</c>. This event is sent as a result of calling <seealso + marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item> <tag><c><![CDATA[{shell, boolean() = WantReply}]]></c></tag> - <item> This message will request that the user's default shell - be started at the other end. This event is sent as result of calling <seealso - marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso> - </item> + <item><p>This message requests that the user default shell + is started at the other end. This event is sent as a result of calling + <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>. + </p></item> <tag><c><![CDATA[{window_change, ssh_channel_id(), integer() = CharWidth, integer() = RowHeight, integer() = PixWidth, integer() = PixHeight}]]></c></tag> - <item> When the window (terminal) size changes on the client - side, it MAY send a message to the server side to inform it of - the new dimensions. There is currently no API function to generate this - event.</item> + <item><p>When the window (terminal) size changes on the client + side, it <em>can</em> send a message to the server side to inform it of + the new dimensions. No API function generates this event.</p></item> <tag><c><![CDATA[{exec, ssh_channel_id(), boolean() = WantReply, string() = Cmd}]]></c></tag> - <item> This message will request that the server starts - execution of the given command. This event is sent as result of calling <seealso - marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso> - </item> + <item><p>This message requests that the server starts + execution of the given command. This event is sent as a result of calling <seealso + marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. + </p></item> </taglist> </item> </taglist> @@ -183,80 +201,83 @@ <func> <name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> - <fsummary>Adjusts the SSH flowcontrol window. </fsummary> + <fsummary>Adjusts the SSH flow control window.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id() </v> - <v> NumOfBytes = integer()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>NumOfBytes = integer()</v> </type> <desc> - <p>Adjusts the SSH flowcontrol window. This shall be done by both client and server side channel processes.</p> + <p>Adjusts the SSH flow control window. This is to be done by both the + client- and server-side channel processes.</p> - <note><p>Channels implemented with the <seealso marker="ssh_channel"> ssh_channel - behavior</seealso> will normaly not need to call this function as flow control - will be handled by the behavior. The behavior will adjust the window every time + <note><p>Channels implemented with the <seealso marker="ssh_channel"> ssh_channel</seealso> + behavior do not normally need to call this function as flow control + is handled by the behavior. The behavior adjusts the window every time the callback <seealso marker="ssh_channel#Module:handle_ssh_msg-2"> - handle_ssh_msg/2 </seealso> has returned after processing channel data</p> </note> + handle_ssh_msg/2</seealso> returns after processing channel data.</p></note> </desc> </func> <func> <name>close(ConnectionRef, ChannelId) -> ok</name> - <fsummary>Sends a close message on the channel <c>ChannelId</c>. </fsummary> + <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> </type> <desc> - <p>A server or client channel process can choose to close their session by sending a close event. + <p>A server- or client-channel process can choose to close their session by + sending a close event. </p> - <note><p>This function will be called by the ssh_channel - behavior when the channel is terminated see <seealso - marker="ssh_channel"> ssh_channel(3) </seealso> so channels implemented with the - behavior should not call this function explicitly.</p></note> + <note><p>This function is called by the <c>ssh_channel</c> + behavior when the channel is terminated, see <seealso + marker="ssh_channel"> ssh_channel(3)</seealso>. Thus, channels implemented + with the behavior are not to call this function explicitly.</p></note> </desc> </func> <func> - <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | {error, reason()} </name> - <fsummary>Request that the server start the execution of the given command. </fsummary> + <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | + {error, reason()}</name> + <fsummary>Requests that the server starts the execution of the given command.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Command = string()</v> - <v>Timeout = timeout() </v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>Command = string()</v> + <v>Timeout = timeout()</v> </type> <desc> - <p>Should be called by a client channel process to request that the server starts execution of the - given command, the result will be several messages according to the following pattern. Note - that the last message will be a channel close message, as the exec request is a one time - execution that closes the channel when it is done.</p> + <p>Is to be called by a client-channel process to request that the server starts + executing the given command. The result is several messages according to the + following pattern. The last message is a channel close message, as the <c>exec</c> + request is a one-time execution that closes the channel when it is done.</p> <taglist> - <tag><c> N x {ssh_cm, ssh_connection_ref(), - {data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}} </c></tag> - <item>The result of executing the command may be only one line - or thousands of lines depending on the command.</item> + <tag><c>N x {ssh_cm, ssh_connection_ref(), + {data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}}</c></tag> + <item><p>The result of executing the command can be only one line + or thousands of lines depending on the command.</p></item> <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {eof, ssh_channel_id()}}</c></tag> - <item>Indicates that no more data will be sent.</item> + <item><p>Indicates that no more data is to be sent.</p></item> <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {exit_signal, ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg, string() = LanguageString}}</c></tag> - <item>Not all systems send signals. For details on valid string - values see RFC 4254 section 6.10 </item> + <item><p>Not all systems send signals. For details on valid string + values, see RFC 4254, Section 6.10</p></item> <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {exit_status, ssh_channel_id(), integer() = ExitStatus}}</c></tag> - <item>It is recommended by the <c>ssh connection protocol</c> that this - message shall be sent, but that may not always be the case.</item> + <item><p>It is recommended by the SSH Connection Protocol to send this + message, but that is not always the case.</p></item> - <tag><c> 1 x {ssh_cm, ssh_connection_ref(), + <tag><c>1 x {ssh_cm, ssh_connection_ref(), {closed, ssh_channel_id()}}</c></tag> - <item>Indicates that the ssh channel started for the - execution of the command has now been shutdown.</item> + <item><p>Indicates that the <c>ssh_channel</c> started for the + execution of the command has now been shut down.</p></item> </taglist> </desc> </func> @@ -265,78 +286,72 @@ <name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name> <fsummary>Sends the exit status of a command to the client.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Status = integer()</v> + <v>ConnectionRef = ssh_connection_ref() </v> + <v>ChannelId = ssh_channel_id()</v> + <v>Status = integer()</v> </type> <desc> - <p>Should be called by a server channel process to sends the exit status of a command to the client.</p> + <p>Is to be called by a server-channel process to send the exit status of a command + to the client.</p> </desc> </func> <func> - <name>ptty_alloc(ConnectionRef, ChannelId, Options) -> </name> - <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | {error, reason()} </name> - <fsummary>Send status replies to requests that want such replies. </fsummary> + <name>ptty_alloc(ConnectionRef, ChannelId, Options) -></name> + <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | + {error, reason()}</name> + <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>, + to allocate a pseudo-terminal.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Options = proplists:proplist()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>Options = proplists:proplist()</v> </type> <desc> - <p> Sends a SSH Connection Protocol pty_req, to allocate a pseudo tty. - Should be called by a SSH client process. - Options are: - </p> + <p>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal. + Is to be called by an SSH client process.</p> + <p>Options:</p> <taglist> <tag>{term, string()}</tag> - <item> - Defaults to os:getenv("TERM") or "vt100" if it is undefined. - </item> + <item><p>Defaults to <em>os:getenv("TERM")</em> or <em>vt100</em> + if it is undefined.</p></item> + <tag>{width, integer()}</tag> - <item> - Defaults to 80 if pixel_width is not defined. - </item> + <item><p>Defaults to 80 if <c>pixel_width</c> is not defined.</p></item> + <tag>{height, integer()}</tag> - <item> - Defaults to 24 if pixel_height is not defined. - </item> + <item><p>Defaults to 24 if <c>pixel_height</c> is not defined.</p></item> + <tag>{pixel_width, integer()}</tag> - <item> - Is disregarded if width is defined. - </item> + <item><p>Is disregarded if <c>width</c> is defined.</p></item> + <tag>{pixel_height, integer()}</tag> - <item> - Is disregarded if height is defined. - </item> + <item><p>Is disregarded if <c>height</c> is defined.</p></item> + <tag>{pty_opts, [{posix_atom(), integer()}]}</tag> - <item> - Option may be an empty list, otherwise - see possible POSIX names in section 8 in <url href="http://www.ietf.org/rfc/rfc4254.txt"> RFC 4254</url>. + <item><p>Option can be an empty list. Otherwise, see possible <em>POSIX</em> names + in Section 8 in <url href="http://www.ietf.org/rfc/rfc4254.txt"> RFC 4254</url>.</p> </item> </taglist> - </desc> </func> - <func> + <func> <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> - <fsummary>Send status replies to requests that want such replies. </fsummary> + <fsummary>Sends status replies to requests that want such replies.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> WantReply = boolean()</v> - <v> Status = ssh_request_status() </v> - <v> ChannelId = ssh_channel_id()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>WantReply = boolean()</v> + <v>Status = ssh_request_status()</v> + <v>ChannelId = ssh_channel_id()</v> </type> <desc> <p>Sends status replies to requests where the requester has - stated that they want a status report e.i .<c> WantReply = true</c>, - if <c> WantReply</c> is false calling this function will be a - "noop". Should be called while handling an ssh connection - protocol message containing a <c>WantReply</c> boolean - value. - </p> + stated that it wants a status report, that is, <c>WantReply = true</c>. + If <c>WantReply</c> is <c>false</c>, calling this function becomes a + "noop". Is to be called while handling an SSH Connection + Protocol message containing a <c>WantReply</c> boolean value.</p> </desc> </func> @@ -346,98 +361,97 @@ <name>send(ConnectionRef, ChannelId, Type, Data) -></name> <name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> ok | {error, timeout} | {error, closed}</name> - <fsummary>Sends channel data </fsummary> + <fsummary>Sends channel data.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Data = binary()</v> - <v> Type = ssh_data_type_code()</v> - <v> Timeout = timeout()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>Data = binary()</v> + <v>Type = ssh_data_type_code()</v> + <v>Timeout = timeout()</v> </type> <desc> - <p>Should be called by client- and server channel processes to send data to each other. + <p>Is to be called by client- and server-channel processes to send data to each other. </p> </desc> </func> <func> <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> - <fsummary>Sends eof on the channel <c>ChannelId</c>. </fsummary> + <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> </type> <desc> - <p>Sends eof on the channel <c>ChannelId</c>. - </p> + <p>Sends EOF on channel <c>ChannelId</c>.</p> </desc> </func> <func> - <name>session_channel(ConnectionRef, Timeout) -> </name> + <name>session_channel(ConnectionRef, Timeout) -></name> <name>session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> {ok, ssh_channel_id()} | {error, reason()}</name> - <fsummary>Opens a channel for a ssh session. </fsummary> + <fsummary>Opens a channel for an SSH session.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref()</v> - <v> InitialWindowSize = integer() </v> - <v> MaxPacketSize = integer() </v> - <v> Timeout = timeout()</v> - <v> Reason = term() </v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>InitialWindowSize = integer()</v> + <v>MaxPacketSize = integer()</v> + <v>Timeout = timeout()</v> + <v>Reason = term()</v> </type> <desc> <p>Opens a channel for an SSH session. The channel id returned from this function - is the id used as input to the other funtions in this module. - </p> + is the id used as input to the other functions in this module.</p> </desc> </func> <func> - <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | {error, reason()} </name> - <fsummary> Environment variables may be passed to the + <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | + {error, reason()}</name> + <fsummary>Environment variables can be passed to the shell/command to be started later.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Var = string()</v> - <v> Value = string()</v> - <v> Timeout = timeout()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>Var = string()</v> + <v>Value = string()</v> + <v>Timeout = timeout()</v> </type> <desc> - <p> Environment variables may be passed before starting the - shell/command. Should be called by a client channel processes. - </p> + <p>Environment variables can be passed before starting the + shell/command. Is to be called by a client channel processes.</p> </desc> </func> <func> <name>shell(ConnectionRef, ChannelId) -> ssh_request_status() | {error, closed} </name> - <fsummary> Requests that the user's default shell (typically - defined in /etc/passwd in UNIX systems) shall be executed at the server - end. </fsummary> + <fsummary>Requests that the user default shell (typically defined in + /etc/passwd in Unix systems) is to be executed at the server end.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> </type> <desc> - <p> Should be called by a client channel process to request that the user's default shell (typically - defined in /etc/passwd in UNIX systems) shall be executed at the server end. - </p> + <p>Is to be called by a client channel process to request that the user default + shell (typically defined in /etc/passwd in Unix systems) is executed + at the server end.</p> </desc> </func> <func> - <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | {error, reason()} </name> - <fsummary> </fsummary> + <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | + {error, reason()}</name> + <fsummary>Requests to execute a predefined subsystem on the server.</fsummary> <type> - <v> ConnectionRef = ssh_connection_ref() </v> - <v> ChannelId = ssh_channel_id()</v> - <v> Subsystem = string()</v> - <v> Timeout = timeout()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>ChannelId = ssh_channel_id()</v> + <v>Subsystem = string()</v> + <v>Timeout = timeout()</v> </type> <desc> - <p> Should be called by a client channel process for requesting to execute a predefined subsystem on the server. + <p>Is to be called by a client-channel process for requesting to execute a predefined + subsystem on the server. </p> </desc> </func> diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml index f7133e4ba5..34ce7f7660 100644 --- a/lib/ssh/doc/src/ssh_server_key_api.xml +++ b/lib/ssh/doc/src/ssh_server_key_api.xml @@ -23,68 +23,81 @@ The Initial Developer of the Original Code is Ericsson AB. </legalnotice> <title>ssh_server_key_api</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> </header> <module>ssh_server_key_api</module> <modulesummary> -behaviour(ssh_server_key_api). </modulesummary> <description> - <p> Behaviour describing the API for an SSH server's public key handling. By implementing the callbacks defined - in this behavior it is possible to customize the SSH server's public key - handling. By default the SSH application implements this behavior - with help of the standard openssh files, see <seealso marker="SSH_app"> ssh(6)</seealso>.</p> + <p>Behaviour describing the API for public key handling of an SSH server. By implementing + the callbacks defined in this behavior, the public key handling of an SSH server can + be customized. By default the SSH application implements this behavior + with help of the standard OpenSSH files, + see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p> </description> <section> - <title>DATA TYPES </title> + <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module - and/or abstractions to indicate the intended use of the data - type. For more details on public key data types - see the <seealso marker="public_key:public_key_records"> public_key user's guide.</seealso> + <p>Type definitions that are used more than once in this module, + or abstractions to indicate the intended use of the data + type, or both. For more details on public key data types, + refer to Section 2 Public Key Records in the + <seealso marker="public_key:public_key_records"> public_key user's guide</seealso>. </p> - <p> boolean() = true | false</p> - <p> string() = [byte()]</p> - <p> public_key() = #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | term()</p> - <p> private_key() = #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</p> - <p> public_key_algorithm() = 'ssh-rsa' | 'ssh-dss' | atom()</p> +<taglist> + <tag><c>boolean()</c></tag> + <item><p>= <c>true | false</c></p></item> + <tag><c>string()</c></tag> + <item><p>= <c>[byte()]</c></p></item> + <tag><c>public_key()</c></tag> + <item><p>= <c>#'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</c></p></item> + <tag><c>private_key()</c></tag> + <item><p>= <c>#'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</c></p></item> + <tag><c>public_key_algorithm()</c></tag> + <item><p>= <c>'ssh-rsa'| 'ssh-dss' | atom()</c></p></item> + </taglist> </section> <funcs> <func> <name>Module:host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name> - <fsummary>Fetches the hosts private key </fsummary> + <fsummary>Fetches the host’s private key.</fsummary> <type> <v>Algorithm = public_key_algorithm()</v> - <d> Host key algorithm. Should support 'ssh-rsa' | 'ssh-dss' but additional algorithms + <d>Host key algorithm. Is to support <c>'ssh-rsa' | 'ssh-dss'</c>, but more algorithms can be handled.</d> - <v> DaemonOptions = proplists:proplist() </v> - <d>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso></d> - <v> Key = private_key()</v> - <d> The private key of the host matching the <c>Algorithm</c></d> - <v>Reason = term() </v> + <v>DaemonOptions = proplists:proplist()</v> + <d>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</d> + <v>Key = private_key()</v> + <d>Private key of the host matching the <c>Algorithm</c>.</d> + <v>Reason = term()</v> </type> <desc> - <p>Fetches the hosts private key</p> + <p>Fetches the private key of the host.</p> </desc> </func> <func> <name>Module:is_auth_key(Key, User, DaemonOptions) -> Result</name> - <fsummary> Checks if the user key is authorized</fsummary> + <fsummary>Checks if the user key is authorized.</fsummary> <type> - <v> Key = public_key() </v> - <d> Normally an RSA or DSA public key but handling of other public keys can be added</d> - <v> User = string()</v> - <d> The user owning the public key</d> - <v> DaemonOptions = proplists:proplist() </v> - <d> Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso></d> - <v> Result = boolean()</v> + <v>Key = public_key()</v> + <d>Normally an RSA or DSA public key, but handling of other public keys can be added</d> + <v>User = string()</v> + <d>User owning the public key.</d> + <v>DaemonOptions = proplists:proplist()</v> + <d>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</d> + <v>Result = boolean()</v> </type> <desc> - <p> Checks if the user key is authorized </p> + <p>Checks if the user key is authorized.</p> </desc> </func> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index ab111562f9..02970bfa42 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -23,131 +23,171 @@ <title>ssh_sftp</title> <prepared>OTP</prepared> + <docno></docno> <date>2005-09-22</date> + <rev></rev> <file>ssh_sftp.sgml</file> </header> <module>ssh_sftp</module> <modulesummary>SFTP client.</modulesummary> <description> - <p>This module implements an SFTP (SSH FTP) client. SFTP is a + <p>This module implements an SSH FTP (SFTP) client. SFTP is a secure, encrypted file transfer service available for SSH.</p> </description> <section> - <title>DATA TYPES </title> - <p>Type definitions that are used more than once in this module - and/or abstractions to indicate the intended use of the data type: + <title>DATA TYPES</title> + <p>Type definitions that are used more than once in this module, + or abstractions to indicate the intended use of the data type, or both: </p> - <p><c>ssh_connection_ref() - opaque to the user - returned by ssh:connect/3</c></p> - <p><c>timeout() = infinity | integer() - in milliseconds.</c></p> + + <taglist> + <tag><c>ssh_connection_ref()</c></tag> + <item><p>Opaque to the user, returned by <c>ssh:connect/3</c></p></item> + <tag><c>timeout()</c></tag> + <item><p>= <c>infinity | integer() in milliseconds. Default infinity.</c></p></item> + </taglist> </section> <section> - <title>TIMEOUTS </title> - <p>If the request functions for the SFTP channel return {error, timeout} - it does not guarantee that the request did not reach the server and was - not performed, it only means that we did not receive an answer from the - server within the time that was expected.</p> + <title>Time-outs</title> + <p>If the request functions for the SFTP channel return <c>{error, timeout}</c>, + it does not guarantee that the request never reached the server and was + not performed. It only means that no answer was received from the + server within the expected time.</p> </section> <funcs> + <func> + <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, Error}</name> + <v>ChannelPid = pid()</v> + <v>Handle = term()</v> + <v>Position = integer()</v> + <v>Len = integer()</v> + <v>N = term()</v> + <v>Reason = term()</v> + + <desc><p>The <c><![CDATA[apread]]></c> function reads from a specified position, + combining the <c><![CDATA[position]]></c> and <c><![CDATA[aread]]></c> functions.</p> + <p><seealso marker="#apread/3">ssh_sftp:apread/4</seealso></p> </desc> + </func> + + <func> + <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, Error}</name> + <fsummary>Reads asynchronously from an open file.</fsummary> + <type> + <v>ChannelPid = pid()</v> + <v>Handle = term()</v> + <v>Position = integer()</v> + <v>Len = integer()</v> + <v>N = term()</v> + <v>Reason = term()</v> + </type> + <desc> + <p>Reads from an open file, without waiting for the result. If the + handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> + is a term guaranteed to be unique between calls of <c><![CDATA[aread]]></c>. + The actual data is sent as a message to the calling process. This + message has the form <c><![CDATA[{async_reply, N, Result}]]></c>, where + <c><![CDATA[Result]]></c> is the result from the read, either <c><![CDATA[{ok, Data}]]></c>, + <c><![CDATA[eof]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p> + </desc> + </func> + <func> - <name>start_channel(ConnectionRef) -> </name> - <name>start_channel(ConnectionRef, Options) -> </name> - <name>start_channel(Host, Options) -></name> - <name>start_channel(Host, Port, Options) -> {ok, Pid} | {ok, Pid, ConnectionRef} | - {error, Reason}</name> - <fsummary>Starts a SFTP client</fsummary> + <name>apwrite(ChannelPid, Handle, Position, Data) -> ok | {error, Reason}</name> + <fsummary>Writes asynchronously to an open file.</fsummary> <type> - <v>Host = string()</v> - <v>ConnectionRef = ssh_connection_ref()</v> - <v>Port = integer()</v> - <v>Options = [{Option, Value}]</v> + <v>ChannelPid = pid()</v> + <v>Handle = term()</v> + <v>Position = integer()</v> + <v>Len = integer()</v> + <v>Data = binary()</v> + <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>If no connection reference is provided, a connection is set - up and the new connection is returned. An SSH channel process - is started to handle the communication with the SFTP server. - The returned pid for this process should be used as input to - all other API functions in this module.</p> + <p><c><![CDATA[apwrite]]></c> writes on a specified position, combining + the <c><![CDATA[position]]></c> and <c><![CDATA[awrite]]></c> operations.</p> + <p><seealso marker="#awrite/3">ssh_sftp:awrite/3</seealso> </p></desc> + </func> - <p>Options are:</p> - <taglist> - <tag><c><![CDATA[{timeout, timeout()}]]></c></tag> - <item> - <p>The timeout is passed to the ssh_channel start function, - and defaults to infinity.</p> - </item> - <tag> - <p><c><![CDATA[{sftp_vsn, integer()}]]></c></p> - </tag> - <item> - <p> - Desired SFTP protocol version. - The actual version will be the minimum of - the desired version and the maximum supported - versions by the SFTP server. - </p> - </item> - </taglist> - <p>All other options are directly passed to - <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a - connection is already provided. </p> + <func> + <name>awrite(ChannelPid, Handle, Data) -> ok | {error, Reason}</name> + <fsummary>Writes asynchronously to an open file.</fsummary> + <type> + <v>ChannelPid = pid()</v> + <v>Handle = term()</v> + <v>Position = integer()</v> + <v>Len = integer()</v> + <v>Data = binary()</v> + <v>Timeout = timeout()</v> + <v>Reason = term()</v> + </type> + <desc> + <p>Writes to an open file, without waiting for the result. If the + handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> + is a term guaranteed to be unique between calls of + <c><![CDATA[awrite]]></c>. The result of the <c><![CDATA[write]]></c> operation is sent + as a message to the calling process. This message has the form + <c><![CDATA[{async_reply, N, Result}]]></c>, where <c><![CDATA[Result]]></c> is the result + from the write, either <c><![CDATA[ok]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p> </desc> </func> <func> - <name>stop_channel(ChannelPid) -> ok</name> - <fsummary>Stops the SFTP client channel.</fsummary> + <name>close(ChannelPid, Handle) -></name> + <name>close(ChannelPid, Handle, Timeout) -> ok | {error, Reason}</name> + <fsummary>Closes an open handle.</fsummary> <type> <v>ChannelPid = pid()</v> + <v>Handle = term()</v> + <v>Timeout = timeout()</v> + <v>Reason = term()</v> </type> <desc> - <p>Stops an SFTP channel. Does not close the SSH connetion. - Use <seealso marker="ssh">ssh:close/1</seealso> to close it.</p> + <p>Closes a handle to an open file or directory on the server.</p> </desc> </func> - + <func> - <name>read_file(ChannelPid, File) -> </name> - <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, Reason}</name> - <fsummary>Read a file</fsummary> + <name>delete(ChannelPid, Name) -></name> + <name>delete(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> + <fsummary>Deletes a file.</fsummary> <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Data = binary()</v> + <v>ChannelPid = pid()</v> + <v>Name = string()</v> <v>Timeout = timeout()</v> - <v>Reason = term()</v> + <v>Reason = term()</v> </type> <desc> - <p>Reads a file from the server, and returns the data in a binary, - like <c><![CDATA[file:read_file/1]]></c>.</p> + <p>Deletes the file specified by <c><![CDATA[Name]]></c>, like + <seealso marker="kernel:file#delete/1">file:delete/1</seealso></p> </desc> </func> + <func> - <name>write_file(ChannelPid, File, Iolist) -> </name> - <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, Reason}</name> - <fsummary>Write a file</fsummary> + <name>del_dir(ChannelPid, Name) -></name> + <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> + <fsummary>Deletes an empty directory.</fsummary> <type> <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Iolist = iolist()</v> + <v>Name = string()</v> <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>Writes a file to the server, like - <c><![CDATA[file:write_file/2]]></c>. The file is created if - it does not exist or is owerwritten if it does.</p> + <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. + The directory must be empty before it can be successfully deleted. + </p> </desc> </func> - <func> - <name>list_dir(ChannelPid, Path) -> </name> + + <func> + <name>list_dir(ChannelPid, Path) -></name> <name>list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, Reason}</name> - <fsummary>List directory</fsummary> + <fsummary>Lists the directory.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Path = string()</v> @@ -161,10 +201,45 @@ filenames as a list of strings.</p> </desc> </func> + + <func> + <name>make_dir(ChannelPid, Name) -></name> + <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> + <fsummary>Creates a directory.</fsummary> + <type> + <v>ChannelPid = pid()</v> + <v>Name = string()</v> + <v>Timeout = timeout()</v> + <v>Reason = term()</v> + </type> + <desc> + <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> + must be a full path to a new directory. The directory can only be + created in an existing directory.</p> + </desc> + </func> + <func> - <name>open(ChannelPid, File, Mode) -> </name> + <name>make_symlink(ChannelPid, Name, Target) -></name> + <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, Reason}</name> + <fsummary>Creates a symbolic link.</fsummary> + <type> + <v>ChannelPid = pid()</v> + <v>Name = string()</v> + <v>Target = string()</v> + <v>Reason = term()</v> + </type> + <desc> + <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the + name <c><![CDATA[Name]]></c>, like + <seealso marker="kernel:file#make_symlink/2">file:make_symlink/2</seealso></p> + </desc> + </func> + + <func> + <name>open(ChannelPid, File, Mode) -></name> <name>open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, Reason}</name> - <fsummary>Open a file and return a handle</fsummary> + <fsummary>Opens a file and returns a handle.</fsummary> <type> <v>ChannelPid = pid()</v> <v>File = string()</v> @@ -175,14 +250,14 @@ <v>Reason = term()</v> </type> <desc> - <p>Opens a file on the server, and returns a handle that + <p>Opens a file on the server and returns a handle, which can be used for reading or writing.</p> </desc> </func> <func> - <name>opendir(ChannelPid, Path) -> </name> + <name>opendir(ChannelPid, Path) -></name> <name>opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, Reason}</name> - <fsummary>Open a directory and return a handle</fsummary> + <fsummary>Opens a directory and returns a handle.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Path = string()</v> @@ -190,7 +265,7 @@ <v>Reason = term()</v> </type> <desc> - <p>Opens a handle to a directory on the server, the handle + <p>Opens a handle to a directory on the server. The handle can be used for reading directory contents.</p> </desc> </func> @@ -198,14 +273,15 @@ <func> <name>open_tar(ChannelPid, Path, Mode) -></name> <name>open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, Reason}</name> - <fsummary>Opens a tar file on the server to which <v>ChannelPid</v> is connected and returns a handle</fsummary> + <fsummary>Opens a tar file on the server to which <c>ChannelPid</c> + is connected and returns a handle.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Path = string()</v> - <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt] </v> + <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt]</v> <v>EncryptOpt = {crypto,{InitFun,EncryptFun,CloseFun}}</v> <v>DecryptOpt = {crypto,{InitFun,DecryptFun}}</v> - <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize}) </v> + <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize})</v> <v>CryptoState = any()</v> <v>ChunkSize = undefined | pos_integer()</v> <v>EncryptFun = (fun(PlainBin,CryptoState) -> EncryptResult)</v> @@ -219,113 +295,86 @@ <v>Reason = term()</v> </type> <desc> - <p>Opens a handle to a tar file on the server associated with <c>ChannelPid</c>. The handle - can be used for remote tar creation and extraction as defined by the + <p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>. + The handle can be used for remote tar creation and extraction, as defined by the <seealso marker="stdlib:erl_tar#init/3">erl_tar:init/3</seealso> function. </p> - <p>An example of writing and then reading a tar file:</p> - <code type="none"> - {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]), - ok = erl_tar:add(HandleWrite, .... ), - ok = erl_tar:add(HandleWrite, .... ), - ... - ok = erl_tar:add(HandleWrite, .... ), - ok = erl_tar:close(HandleWrite), - - %% And for reading - {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read]), - {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), - ok = erl_tar:close(HandleRead), - </code> - - <p>The <c>crypto</c> mode option is applied to the generated stream of bytes just prior to sending - them to the sftp server. This is intended for encryption but could of course be used for other + + <p> For code exampel see Section + <seealso marker="using_ssh">SFTP Client with TAR Compression and Encryption</seealso> in + the ssh Users Guide. </p> + + <p>The <c>crypto</c> mode option is applied to the generated stream of bytes prior to sending + them to the SFTP server. This is intended for encryption but can be used for other purposes. </p> <p>The <c>InitFun</c> is applied once - prior to any other crypto operation. The returned <c>CryptoState</c> is then folded into - repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned - from those Funs are sent further to the remote sftp server. Finally - if doing encryption - - the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is + prior to any other <c>crypto</c> operation. The returned <c>CryptoState</c> is then folded into + repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned + from those funs are sent further to the remote SFTP server. Finally, if doing encryption, + the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is responsible for padding (if needed) and encryption of that last piece. </p> <p>The <c>ChunkSize</c> defines the size of the <c>PlainBin</c>s that <c>EncodeFun</c> is applied - to. If the <c>ChunkSize</c> is <c>undefined</c> the size of the <c>PlainBin</c>s varies because - this is inteded for stream crypto while a fixed <c>ChunkSize</c> is intended for block crypto. It - is possible to change the <c>ChunkSize</c>s in the return from the <c>EncryptFun</c> or - <c>DecryptFun</c>. It is in fact possible to change the value between <c>pos_integer()</c> and - <c>undefined</c>. + to. If the <c>ChunkSize</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, + because this is intended for stream crypto, whereas a fixed <c>ChunkSize</c> is intended for block crypto. + <c>ChunkSize</c>s can be changed in the return from the <c>EncryptFun</c> or + <c>DecryptFun</c>. The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. </p> - <p>The write and read example above can be extended with encryption and decryption:</p> - <code type="none"> - %% First three parameters depending on which crypto type we select: - Key = <<"This is a 256 bit key. abcdefghi">>, - Ivec0 = crypto:rand_bytes(16), - DataSize = 1024, % DataSize rem 16 = 0 for aes_cbc - - %% Initialization of the CryptoState, in this case it is the Ivector. - InitFun = fun() -> {ok, Ivec0, DataSize} end, - - %% How to encrypt: - EncryptFun = - fun(PlainBin,Ivec) -> - EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin), - {ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)} - end, - - %% What to do with the very last block: - CloseFun = - fun(PlainBin, Ivec) -> - EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, - pad(16,PlainBin) %% Last chunk - ), - {ok, EncryptedBin} - end, - - Cw = {InitFun,EncryptFun,CloseFun}, - {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]), - ok = erl_tar:add(HandleWrite, .... ), - ok = erl_tar:add(HandleWrite, .... ), - ... - ok = erl_tar:add(HandleWrite, .... ), - ok = erl_tar:close(HandleWrite), - - %% And for decryption (in this crypto example we could use the same InitFun - %% as for encryption): - DecryptFun = - fun(EncryptedBin,Ivec) -> - PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin), - {ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)} - end, - - Cr = {InitFun,DecryptFun}, - {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cw}]), - {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), - ok = erl_tar:close(HandleRead), - </code> + </desc> </func> <func> - <name>close(ChannelPid, Handle) -> </name> - <name>close(ChannelPid, Handle, Timeout) -> ok | {error, Reason}</name> - <fsummary>Close an open handle</fsummary> + <name>position(ChannelPid, Handle, Location) -></name> + <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, Error}</name> + <fsummary>Sets the file position of a file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Handle = term()</v> + <v>Location = Offset + | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v> + <v>Offset = integer()</v> <v>Timeout = timeout()</v> + <v>NewPosition = integer()</v> <v>Reason = term()</v> </type> <desc> - <p>Closes a handle to an open file or directory on the server.</p> + <p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>. + Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if + successful, otherwise <c><![CDATA[{error, Reason}]]></c>. <c><![CDATA[Location]]></c> is + one of the following:</p> + <taglist> + <tag><c><![CDATA[Offset]]></c></tag> + <item> + <p>The same as <c><![CDATA[{bof, Offset}]]></c>.</p> + </item> + <tag><c><![CDATA[{bof, Offset}]]></c></tag> + <item> + <p>Absolute offset.</p> + </item> + <tag><c><![CDATA[{cur, Offset}]]></c></tag> + <item> + <p>Offset from the current position.</p> + </item> + <tag><c><![CDATA[{eof, Offset}]]></c></tag> + <item> + <p>Offset from the end of file.</p> + </item> + <tag><c><![CDATA[bof | cur | eof]]></c></tag> + <item> + <p>The same as eariler with <c><![CDATA[Offset]]></c> 0, + that is, <c><![CDATA[{bof, 0} | {cur, 0} | {eof, 0}]]></c>. + </p> + </item> + </taglist> </desc> </func> + <func> - <name>read(ChannelPid, Handle, Len) -> </name> - <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name> - <name>pread(ChannelPid, Handle, Position, Len) -> </name> + <name>pread(ChannelPid, Handle, Position, Len) -></name> <name>pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name> - <fsummary>Read from an open file</fsummary> + <fsummary>Reads from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Handle = term()</v> @@ -336,47 +385,16 @@ <v>Reason = term()</v> </type> <desc> - <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by - <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or - <c><![CDATA[{error, Reason}]]></c>. If the file is opened with <c><![CDATA[binary]]></c>, - <c><![CDATA[Data]]></c> is a binary, otherwise it is a string.</p> - <p>If the file is read past eof, only the remaining bytes - will be read and returned. If no bytes are read, <c><![CDATA[eof]]></c> - is returned.</p> - <p>The <c><![CDATA[pread]]></c> function reads from a specified position, - combining the <c><![CDATA[position]]></c> and <c><![CDATA[read]]></c> functions.</p> - </desc> - </func> - <func> - <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, Error}</name> - <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, Error}</name> - <fsummary>Read asynchronously from an open file</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Reads from an open file, without waiting for the result. If the - handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where N - is a term guaranteed to be unique between calls of <c><![CDATA[aread]]></c>. - The actual data is sent as a message to the calling process. This - message has the form <c><![CDATA[{async_reply, N, Result}]]></c>, where - <c><![CDATA[Result]]></c> is the result from the read, either <c><![CDATA[{ok, Data}]]></c>, - or <c><![CDATA[eof]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p> - <p>The <c><![CDATA[apread]]></c> function reads from a specified position, - combining the <c><![CDATA[position]]></c> and <c><![CDATA[aread]]></c> functions.</p> + <p>The <c><![CDATA[pread]]></c> function reads from a specified position, + combining the <c><![CDATA[position]]></c> and <c><![CDATA[read]]></c> functions.</p> + <p><seealso marker="#read/4">ssh_sftp:read/4</seealso></p> </desc> </func> + <func> - <name>write(ChannelPid, Handle, Data) -></name> - <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, Error}</name> - <name>pwrite(ChannelPid, Handle, Position, Data) -> ok </name> + <name>pwrite(ChannelPid, Handle, Position, Data) -> ok</name> <name>pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, Error}</name> - <fsummary>Write to an open file</fsummary> + <fsummary>Writes to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Handle = term()</v> @@ -386,94 +404,59 @@ <v>Reason = term()</v> </type> <desc> - <p>Writes<c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>. - The file should be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c> - flag. Returns <c><![CDATA[ok]]></c> if successful or S<c><![CDATA[{error, Reason}]]></c> - otherwise.</p> - <p>Typical error reasons are:</p> - <taglist> - <tag><c><![CDATA[ebadf]]></c></tag> - <item> - <p>The file is not opened for writing.</p> - </item> - <tag><c><![CDATA[enospc]]></c></tag> - <item> - <p>There is a no space left on the device.</p> - </item> - </taglist> + <p>The <c><![CDATA[pread]]></c> function writes to a specified position, + combining the <c><![CDATA[position]]></c> and <c><![CDATA[write]]></c> functions.</p> + <p><seealso marker="#write/3">ssh_sftp:write/3</seealso></p> </desc> </func> - <func> - <name>awrite(ChannelPid, Handle, Data) -> ok | {error, Reason} </name> - <name>apwrite(ChannelPid, Handle, Position, Data) -> ok | {error, Reason}</name> - <fsummary>Write asynchronously to an open file</fsummary> + + + <func> + <name>read(ChannelPid, Handle, Len) -></name> + <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name> + <fsummary>Reads from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Handle = term()</v> <v>Position = integer()</v> <v>Len = integer()</v> - <v>Data = binary()</v> <v>Timeout = timeout()</v> + <v>Data = string() | binary()</v> <v>Reason = term()</v> </type> <desc> - <p>Writes to an open file, without waiting for the result. If the - handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where N - is a term guaranteed to be unique between calls of - <c><![CDATA[awrite]]></c>. The result of the <c><![CDATA[write]]></c> operation is sent - as a message to the calling process. This message has the form - <c><![CDATA[{async_reply, N, Result}]]></c>, where <c><![CDATA[Result]]></c> is the result - from the write, either <c><![CDATA[ok]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p> - <p>The <c><![CDATA[apwrite]]></c> writes on a specified position, combining - the <c><![CDATA[position]]></c> and <c><![CDATA[awrite]]></c> operations.</p> + <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by + <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or + <c><![CDATA[{error, Reason}]]></c>. If the file is opened with <c><![CDATA[binary]]></c>, + <c><![CDATA[Data]]></c> is a binary, otherwise it is a string.</p> + <p>If the file is read past <c>eof</c>, only the remaining bytes + are read and returned. If no bytes are read, <c><![CDATA[eof]]></c> + is returned.</p> </desc> </func> - <func> - <name>position(ChannelPid, Handle, Location) -> </name> - <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, Error}</name> - <fsummary>Seek position in open file</fsummary> + + <func> + <name>read_file(ChannelPid, File) -></name> + <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, Reason}</name> + <fsummary>Reads a file.</fsummary> <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Location = Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v> - <v>Offset = integer()</v> + <v>ChannelPid = pid()</v> + <v>File = string()</v> + <v>Data = binary()</v> <v>Timeout = timeout()</v> - <v>NewPosition = integer()</v> - <v>Reason = term()</v> + <v>Reason = term()</v> </type> <desc> - <p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>. - Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if - successful, otherwise <c><![CDATA[{error, Reason}]]></c>. <c><![CDATA[Location]]></c> is - one of the following:</p> - <taglist> - <tag><c><![CDATA[Offset]]></c></tag> - <item> - <p>The same as <c><![CDATA[{bof, Offset}]]></c>.</p> - </item> - <tag><c><![CDATA[{bof, Offset}]]></c></tag> - <item> - <p>Absolute offset.</p> - </item> - <tag><c><![CDATA[{cur, Offset}]]></c></tag> - <item> - <p>Offset from the current position.</p> - </item> - <tag><c><![CDATA[{eof, Offset}]]></c></tag> - <item> - <p>Offset from the end of file.</p> - </item> - <tag><c><![CDATA[bof | cur | eof]]></c></tag> - <item> - <p>The same as above with <c><![CDATA[Offset]]></c> 0.</p> - </item> - </taglist> + <p>Reads a file from the server, and returns the data in a binary, + like + <seealso marker="kernel:file#read_file/1">file:read_file/1</seealso></p> </desc> </func> - <func> - <name>read_file_info(ChannelPid, Name) -> </name> + + <func> + <name>read_file_info(ChannelPid, Name) -></name> <name>read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name> - <fsummary>Get information about a file</fsummary> + <fsummary>Gets information about a file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Name = string()</v> @@ -484,137 +467,190 @@ </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the file specified by - <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like <c><![CDATA[file:read_file_info/2]]></c>.</p> + <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, + like <seealso marker="kernel:file#read_file_info/2">file:read_file_info/2</seealso></p> </desc> </func> - <func> - <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, Reason}</name> - <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name> - <fsummary>Get information about a symbolic link</fsummary> + + <func> + <name>read_link(ChannelPid, Name) -></name> + <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, Reason}</name> + <fsummary>Reads symbolic link.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> + <v>Target = string()</v> <v>Reason = term()</v> </type> <desc> - <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic - link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like - <c><![CDATA[file:read_link_info/2]]></c>.</p> + <p>Reads the link target from the symbolic link specified + by <c><![CDATA[name]]></c>, like + <seealso marker="kernel:file#read_link/1">file:read_link/1</seealso></p> </desc> </func> - <func> - <name>write_file_info(ChannelPid, Name, Info) -> </name> - <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, Reason}</name> - <fsummary>Write information for a file</fsummary> + + <func> + <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, Reason}</name> + <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name> + <fsummary>Gets information about a symbolic link.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Name = string()</v> - <v>Info = record()</v> + <v>Handle = term()</v> <v>Timeout = timeout()</v> + <v>FileInfo = record()</v> <v>Reason = term()</v> </type> <desc> - <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the - file specified by <c><![CDATA[Name]]></c>, like <c><![CDATA[file:write_file_info]]></c>.</p> + <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic + link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like + <seealso marker="kernel:file#read_link_info/2">file:read_link_info/2</seealso></p> </desc> </func> + <func> - <name>read_link(ChannelPid, Name) -> </name> - <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, Reason}</name> - <fsummary>Read symbolic link</fsummary> + <name>rename(ChannelPid, OldName, NewName) -> </name> + <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, Reason}</name> + <fsummary>Renames a file.</fsummary> <type> <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> + <v>OldName = string()</v> + <v>NewName = string()</v> + <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>Reads the link target from the symbolic link specified - by <c><![CDATA[name]]></c>, like <c><![CDATA[file:read_link/1]]></c>.</p> + <p>Renames a file named <c><![CDATA[OldName]]></c> and gives it the name + <c><![CDATA[NewName]]></c>, like + <seealso marker="kernel:file#rename/2">file:rename/2</seealso></p> </desc> </func> + <func> - <name>make_symlink(ChannelPid, Name, Target) -> </name> - <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, Reason}</name> - <fsummary>Create symbolic link</fsummary> + <name>start_channel(ConnectionRef) -></name> + <name>start_channel(ConnectionRef, Options) -></name> + <name>start_channel(Host, Options) -></name> + <name>start_channel(Host, Port, Options) -> {ok, Pid} | {ok, Pid, ConnectionRef} | + {error, Reason}</name> + <fsummary>Starts an SFTP client.</fsummary> <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> + <v>Host = string()</v> + <v>ConnectionRef = ssh_connection_ref()</v> + <v>Port = integer()</v> + <v>Options = [{Option, Value}]</v> <v>Reason = term()</v> </type> <desc> - <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the - name <c><![CDATA[Name]]></c>, like <c><![CDATA[file:make_symlink/2]]></c>.</p> + <p>If no connection reference is provided, a connection is set + up, and the new connection is returned. An SSH channel process + is started to handle the communication with the SFTP server. + The returned <c>pid</c> for this process is to be used as input to + all other API functions in this module.</p> + + <p>Options:</p> + <taglist> + <tag><c><![CDATA[{timeout, timeout()}]]></c></tag> + <item> + <p>The time-out is passed to the <c>ssh_channel</c> start function, + and defaults to <c>infinity</c>.</p> + </item> + <tag> + <c><![CDATA[{sftp_vsn, integer()}]]></c> + </tag> + <item> + <p> + Desired SFTP protocol version. + The actual version is the minimum of + the desired version and the maximum supported + versions by the SFTP server. + </p> + </item> + </taglist> + <p>All other options are directly passed to + <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a + connection is already provided.</p> </desc> </func> - <func> - <name>rename(ChannelPid, OldName, NewName) -> </name> - <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, Reason}</name> - <fsummary>Rename a file</fsummary> + + <func> + <name>stop_channel(ChannelPid) -> ok</name> + <fsummary>Stops the SFTP client channel.</fsummary> <type> <v>ChannelPid = pid()</v> - <v>OldName = string()</v> - <v>NewName = string()</v> - <v>Timeout = timeout()</v> - <v>Reason = term()</v> </type> <desc> - <p>Renames a file named <c><![CDATA[OldName]]></c>, and gives it the name - <c><![CDATA[NewName]]></c>, like <c><![CDATA[file:rename/2]]></c></p> + <p>Stops an SFTP channel. Does not close the SSH connection. + Use <seealso marker="ssh#close/1">ssh:close/1</seealso> to close it.</p> </desc> </func> + <func> - <name>delete(ChannelPid, Name) -> </name> - <name>delete(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> - <fsummary>Delete a file</fsummary> + <name>write_file(ChannelPid, File, Iolist) -></name> + <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, Reason}</name> + <fsummary>Writes a file.</fsummary> <type> <v>ChannelPid = pid()</v> - <v>Name = string()</v> + <v>File = string()</v> + <v>Iolist = iolist()</v> <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>Deletes the file specified by <c><![CDATA[Name]]></c>, like - <c><![CDATA[file:delete/1]]></c></p> + <p>Writes a file to the server, like <seealso + marker="kernel:file#write_file/2">file:write_file/2</seealso> The + file is created if it does not exist. The file is overwritten + if it exists.</p> </desc> </func> + <func> - <name>make_dir(ChannelPid, Name) -> </name> - <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> - <fsummary>Create a directory</fsummary> + <name>write(ChannelPid, Handle, Data) -></name> + <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, Error}</name> + <fsummary>Writes to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> - <v>Name = string()</v> + <v>Handle = term()</v> + <v>Position = integer()</v> + <v>Data = iolist()</v> <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> should - be a full path to a new directory. The directory can only be - created in an existing directory.</p> + <p>Writes <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>. + The file is to be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c> + flag. Returns <c><![CDATA[ok]]></c> if successful or <c><![CDATA[{error, Reason}]]></c> + otherwise.</p> + <p>Typical error reasons:</p> + <taglist> + <tag><c><![CDATA[ebadf]]></c></tag> + <item> + <p>File is not opened for writing.</p> + </item> + <tag><c><![CDATA[enospc]]></c></tag> + <item> + <p>No space is left on the device.</p> + </item> + </taglist> </desc> </func> + <func> - <name>del_dir(ChannelPid, Name) -> </name> - <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name> - <fsummary>Delete an empty directory</fsummary> + <name>write_file_info(ChannelPid, Name, Info) -></name> + <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, Reason}</name> + <fsummary>Writes information for a file.</fsummary> <type> <v>ChannelPid = pid()</v> <v>Name = string()</v> + <v>Info = record()</v> <v>Timeout = timeout()</v> <v>Reason = term()</v> </type> <desc> - <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. - Note that the directory must be empty before it can be successfully deleted - </p> + <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the + file specified by <c><![CDATA[Name]]></c>, like + <seealso marker="kernel:file#write_file_info/2">file:write_file_info/[2,3]</seealso></p> </desc> </func> - </funcs> </erlref> diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml index 81c2acc575..bc2660f595 100644 --- a/lib/ssh/doc/src/ssh_sftpd.xml +++ b/lib/ssh/doc/src/ssh_sftpd.xml @@ -22,67 +22,73 @@ </legalnotice> <title>ssh_sftpd</title> + <prepared></prepared> + <docno></docno> <date>2005-09-22</date> + <rev></rev> <file>ssh_sftpd.sgml</file> </header> <module>ssh_sftpd</module> - <modulesummary>Specifies the channel process to handle an sftp subsystem.</modulesummary> + <modulesummary>Specifies the channel process to handle an SFTP subsystem.</modulesummary> <description> - <p>Specifies a channel process to handle a sftp subsystem.</p> + <p>Specifies a channel process to handle an SFTP subsystem.</p> </description> <section> - <title>DATA TYPES </title> - <p><c>subsystem_spec() = {subsystem_name(), {channel_callback(), channel_init_args()}} </c></p> - <p><c>subsystem_name() = "sftp"</c></p> - <p><c>channel_callback() = atom()</c> - Name of the erlang module implementing the - subsystem using the ssh_channel behavior see - <seealso marker="ssh_channel">ssh_channel(3)</seealso></p> - <p><c> channel_init_args() = list() - The one given as argument to function - subsystem_spec/1.</c></p> + <title>DATA TYPES</title> + <taglist> + <tag><c>subsystem_spec()</c></tag> + <item><p>= <c>{subsystem_name(), {channel_callback(), channel_init_args()}}</c></p></item> + <tag><c>subsystem_name()</c></tag> + <item><p>= <c>"sftp"</c></p></item> + <tag><c>channel_callback()</c></tag> + <item><p>= <c>atom()</c> - Name of the Erlang module implementing the subsystem using the + <c>ssh_channel</c> behavior, see the + <seealso marker="ssh_channel">ssh_channel(3)</seealso> manual page.</p></item> + <tag><c>channel_init_args()</c></tag> + <item><p>= <c>list()</c> - The one given as argument to function <c>subsystem_spec/1</c>.</p></item> + </taglist> </section> <funcs> <func> <name>subsystem_spec(Options) -> subsystem_spec()</name> - <fsummary>Returns the subsystem specification that allows an ssh daemon to handle the subsystem "sftp".</fsummary> + <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary> <type> <v>Options = [{Option, Value}]</v> </type> <desc> - <p>Should be used together with ssh:daemon/[1,2,3]</p> - <p>Options are:</p> + <p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p> + <p>Options:</p> <taglist> <tag><c><![CDATA[{cwd, String}]]></c></tag> <item> - <p>Sets the initial current working directory for the - server.</p> + <p>Sets the initial current working directory for the server.</p> </item> <tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag> <item> <p>Determines which module to call for accessing - the file server. The default value is <c>ssh_sftpd_file</c> that uses the - <seealso marker="kernel:file">file</seealso> and <seealso marker="stdlib:filelib">filelib</seealso> API:s to access the standard OTP file - server. This option may be used to plug in + the file server. The default value is <c>ssh_sftpd_file</c>, which uses the + <seealso marker="kernel:file">file</seealso> and <seealso marker="stdlib:filelib">filelib</seealso> + APIs to access the standard OTP file server. This option can be used to plug in other file servers.</p> </item> <tag><c><![CDATA[{max_files, Integer}]]></c></tag> <item> <p>The default value is <c>0</c>, which means that there is no upper limit. - If supplied, the number of filenames returned to the sftp client per <c>READDIR</c> + If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c> request is limited to at most the given value.</p> </item> <tag><c><![CDATA[{root, String}]]></c></tag> <item> - <p>Sets the sftp root directory. The user will then not be - able to see any files above this root. If for instance - the root is set to <c>/tmp</c> the user will see this - directory as <c>/</c> and if the user does cd <c>/etc</c> - the user will end up in <c>/tmp/etc</c>. + <p>Sets the SFTP root directory. Then the user cannot see any files + above this root. If, for example, the root directory is set to <c>/tmp</c>, + then the user sees this directory as <c>/</c>. If the user then writes + <c>cd /etc</c>, the user moves to <c>/tmp/etc</c>. </p> </item> <tag><c><![CDATA[{sftpd_vsn, integer()}]]></c></tag> <item> - <p>Sets the sftp version to use, defaults to 5. Version 6 is under + <p>Sets the SFTP version to use. Defaults to 5. Version 6 is under development and limited.</p> </item> </taglist> diff --git a/lib/ssh/doc/src/usersguide.xml b/lib/ssh/doc/src/usersguide.xml index 8ab14c2945..a9ed5fe21e 100644 --- a/lib/ssh/doc/src/usersguide.xml +++ b/lib/ssh/doc/src/usersguide.xml @@ -23,15 +23,16 @@ <title>SSH User's Guide</title> <prepared>OTP Team</prepared> + <docno></docno> <date>2012-10-11</date> + <rev></rev> <file>usersguide.xml</file> </header> <description> - <p>The <em>SSH</em> application implements the SSH (Secure Shell) protocol and - provides an SFTP (Secret File Transfer Protocol) client and server. + <p>The Erlang Secure Shell (SSH) application, <c>ssh</c>, implements the SSH Transport Layer Protocol and + provides SSH File Transfer Protocol (SFTP) clients and servers. </p> </description> <xi:include href="introduction.xml"/> - <xi:include href="ssh_protocol.xml"/> <xi:include href="using_ssh.xml"/> </part> diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml index 46178d4018..cd7b64ac43 100644 --- a/lib/ssh/doc/src/using_ssh.xml +++ b/lib/ssh/doc/src/using_ssh.xml @@ -22,64 +22,70 @@ </legalnotice> - <title>Getting started</title> + <title>Getting Started</title> + <prepared></prepared> + <docno></docno> + <approved></approved> + <date></date> + <rev></rev> <file>using_ssh.xml</file> </header> <section> - <title> General information</title> - <p>The examples in the following sections use the utility function - <seealso marker="ssh#start-0"> ssh:start/0 </seealso> that starts - all needed applications (crypto, public_key and ssh). All examples - are run in an Erlang shell, or in a bash shell using openssh to - illustrate how the erlang ssh application can be used. The - examples are run as the user otptest on a local network where the - user is authorized to login in over ssh to the host "tarlop". If - nothing else is stated it is persumed that the otptest user has an - entry in tarlop's authorized_keys file (may log in via ssh without - entering a password). Also tarlop is a known host in the user - otptest's known_hosts file so that host verification can be done - without user interaction. + <title>General Information</title> + <p>The following examples use the utility function + <seealso marker="ssh#start-0"> ssh:start/0</seealso> to start + all needed applications (<c>crypto</c>, <c>public_key</c>, and <c>ssh</c>). + All examples are run in an Erlang shell, or in a bash shell, using <em>openssh</em> + to illustrate how the <c>ssh</c> application can be used. The + examples are run as the user <c>otptest</c> on a local network where the + user is authorized to log in over <c>ssh</c> to the host <em>tarlop</em>. + </p> + <p>If nothing else is stated, it is presumed that the <c>otptest</c> user + has an entry in the <em>authorized_keys</em> file of <em>tarlop</em> + (allowed to log in over <c>ssh</c> without entering a password). + Also, <em>tarlop</em> is a known host in the <c>known_hosts</c> + file of the user <c>otptest</c>. This means that host-verification + can be done without user-interaction. </p> </section> <section> - <title>Using the Erlang SSH Terminal Client</title> + <title>Using the Erlang ssh Terminal Client</title> - <p>The user otptest, that has bash as default shell, uses the - ssh:shell/1 client to connect to the openssh daemon running on a - host called tarlop. Note that currently this client is very simple - and you should not be expected to be as fancy as the openssh - client.</p> + <p>The user <c>otptest</c>, which has bash as default shell, uses the + <c>ssh:shell/1</c> client to connect to the <em>openssh</em> daemon running on a + host called <em>tarlop</em>:</p> <code type="erl" > 1> ssh:start(). ok 2> {ok, S} = ssh:shell("tarlop"). - >pwd + otptest@tarlop:> pwd /home/otptest - >exit + otptest@tarlop:> exit logout 3> </code> </section> <section> - <title>Running an Erlang SSH Daemon </title> + <marker id="Running an Erlang ssh Daemon"></marker> + <title>Running an Erlang ssh Daemon</title> - <p> The option system_dir must be a directory containing a host - key file and it defaults to /etc/ssh. For details see section + <p>The <c>system_dir</c> option must be a directory containing a host + key file and it defaults to <c>/etc/ssh</c>. For details, see Section Configuration Files in <seealso marker="SSH_app">ssh(6)</seealso>. </p> - <note><p>Normally the /etc/ssh directory is only readable by root. </p> + <note><p>Normally, the <c>/etc/ssh</c> directory is only readable by root.</p> </note> - <p> The option user_dir defaults to the users ~/.ssh directory</p> + <p>The option <c>user_dir</c> defaults to directory <c>users ~/.ssh</c>.</p> - <p>In the following example we generate new keys and host keys as - to be able to run the example without having root privileges</p> + <p><em>Step 1.</em> To run the example without root privileges, + generate new keys and host keys:</p> <code> $bash> ssh-keygen -t rsa -f /tmp/ssh_daemon/ssh_host_rsa_key @@ -88,19 +94,22 @@ [...] </code> - <p>Create the file /tmp/otptest_user/.ssh/authorized_keys and add the content - of /tmp/otptest_user/.ssh/id_rsa.pub Now we can do</p> + <p><em>Step 2.</em> Create the file <c>/tmp/otptest_user/.ssh/authorized_keys</c> + and add the content of <c>/tmp/otptest_user/.ssh/id_rsa.pub</c>.</p> + + <p><em>Step 3.</em> Start the Erlang <c>ssh</c> daemon:</p> <code type="erl"> 1> ssh:start(). ok - 2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, - {user_dir, "/tmp/otptest_user/.ssh"}]). + 2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, + {user_dir, "/tmp/otptest_user/.ssh"}]). {ok,<0.54.0>} 3> </code> - <p>Use the openssh client from a shell to connect to the Erlang ssh daemon.</p> + <p><em>Step 4.</em> Use the <em>openssh</em> client from a shell to connect + to the Erlang <c>ssh</c> daemon:</p> <code> $bash> ssh tarlop -p 8989 -i /tmp/otptest_user/.ssh/id_rsa\ @@ -113,9 +122,12 @@ 1> </code> - <p>There are two ways of shutting down an SSH daemon</p> + <p>There are two ways of shutting down an <c>ssh</c> daemon, + see <em>Step 5a</em> and <em>Step 5b</em>.</p> - <p>1: Stops the listener, but leaves existing connections started by the listener up and running.</p> + <p><em>Step 5a.</em> Shut down the Erlang <c>ssh</c> daemon so that it + stops the listener but leaves existing connections, started by the listener, + operational:</p> <code type="erl"> 3> ssh:stop_listener(Sshd). @@ -123,7 +135,8 @@ 4> </code> - <p>2: Stops the listener and all connections started by the listener.</p> + <p><em>Step 5b.</em> Shut down the Erlang <c>ssh</c> daemon so that it + stops the listener and all connections started by the listener:</p> <code type="erl"> 3> ssh:stop_daemon(Sshd) @@ -134,17 +147,18 @@ </section> <section> - <title>One Time Execution</title> + <title>One-Time Execution</title> - <p>In the following example the Erlang shell is the client process - that receives the channel replies. </p> + <p>In the following example, the Erlang shell is the client process + that receives the channel replies.</p> - <note><p> If you run this example - in your environment you may get fewer or more messages back as - this depends on the OS and shell on the machine running the ssh - daemon. See also <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso> + <note><p>The number of received messages in this example depends on which OS + and which shell that is used on the machine running the <c>ssh</c> daemon. + See also <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>. </p></note> + <p>Do a one-time execution of a remote command over <c>ssh</c>:</p> + <code type="erl" > 1> ssh:start(). ok @@ -162,7 +176,8 @@ 6> </code> - <p>Note only the channel is closed the connection is still up and can handle other channels</p> + <p>Notice that only the channel is closed. The connection is still up and can + handle other channels:</p> <code type="erl" > 6> {ok, NewChannelId} = ssh_connection:session_channel(ConnectionRef, infinity). @@ -172,19 +187,22 @@ </section> <section> - <title>SFTP (SSH File Transport Protocol) server</title> + <title>SFTP Server</title> + + <p>Start the Erlang <c>ssh</c> daemon with the SFTP subsystem:</p> <code type="erl" > 1> ssh:start(). ok - 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, - {user_dir, "/tmp/otptest_user/.ssh"}, - {subsystems, [ssh_sftpd:subsystem_spec([{cwd, "/tmp/sftp/example"}])]}]). + 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, + {user_dir, "/tmp/otptest_user/.ssh"}, + {subsystems, [ssh_sftpd:subsystem_spec([{cwd, "/tmp/sftp/example"}]) + ]}]). {ok,<0.54.0>} 3> </code> - <p> Run the openssh sftp client</p> + <p>Run the OpenSSH SFTP client:</p> <code type="erl"> $bash> sftp -oPort=8989 -o IdentityFile=/tmp/otptest_user/.ssh/id_rsa\ @@ -197,7 +215,9 @@ </section> <section> - <title>SFTP (SSH File Transport Protocol) client</title> + <title>SFTP Client</title> + + <p>Fetch a file with the Erlang SFTP client:</p> <code type="erl" > 1> ssh:start(). @@ -210,10 +230,77 @@ </section> <section> - <title>Creating a subsystem</title> + <title>SFTP Client with TAR Compression and Encryption</title> + + <p>Example of writing and then reading a tar file follows:</p> + <code type="erlang"> + {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]), + ok = erl_tar:add(HandleWrite, .... ), + ok = erl_tar:add(HandleWrite, .... ), + ... + ok = erl_tar:add(HandleWrite, .... ), + ok = erl_tar:close(HandleWrite), + + %% And for reading + {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read]), + {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), + ok = erl_tar:close(HandleRead), + </code> + + <p>The previous write and read example can be extended with encryption and decryption as follows:</p> + <code type="erlang"> +%% First three parameters depending on which crypto type we select: +Key = <<"This is a 256 bit key. abcdefghi">>, +Ivec0 = crypto:rand_bytes(16), +DataSize = 1024, % DataSize rem 16 = 0 for aes_cbc + +%% Initialization of the CryptoState, in this case it is the Ivector. +InitFun = fun() -> {ok, Ivec0, DataSize} end, + +%% How to encrypt: +EncryptFun = + fun(PlainBin,Ivec) -> + EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin), + {ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)} + end, + +%% What to do with the very last block: +CloseFun = + fun(PlainBin, Ivec) -> + EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, + pad(16,PlainBin) %% Last chunk + ), + {ok, EncryptedBin} + end, + +Cw = {InitFun,EncryptFun,CloseFun}, +{ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]), +ok = erl_tar:add(HandleWrite, .... ), +ok = erl_tar:add(HandleWrite, .... ), +... +ok = erl_tar:add(HandleWrite, .... ), +ok = erl_tar:close(HandleWrite), + +%% And for decryption (in this crypto example we could use the same InitFun +%% as for encryption): +DecryptFun = + fun(EncryptedBin,Ivec) -> + PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin), + {ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)} + end, + +Cr = {InitFun,DecryptFun}, +{ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cw}]), +{ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), +ok = erl_tar:close(HandleRead), + </code> + </section> + + <section> + <title>Creating a Subsystem</title> - <p>A very small SSH subsystem that echos N bytes could be implemented like this. - See also <seealso marker="ssh_channel"> ssh_channel(3)</seealso> </p> + <p>A small <c>ssh</c> subsystem that echoes N bytes can be implemented as shown + in the following example:</p> <code type="erl" > -module(ssh_echo_server). @@ -267,14 +354,16 @@ terminate(_Reason, _State) -> ok. </code> - <p>And run like this on the host tarlop with the keys generated in section 3.3</p> + <p>The subsystem can be run on the host <em>tarlop</em> with the generated keys, + as described in Section <seealso marker="#Running an Erlang ssh Daemon"> + Running an Erlang ssh Daemon</seealso>:</p> <code type="erl" > 1> ssh:start(). ok - 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, - {user_dir, "/tmp/otptest_user/.ssh"} - {subsystems, [{"echo_n", {ssh_echo_server, [10]}}]}]). + 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"}, + {user_dir, "/tmp/otptest_user/.ssh"} + {subsystems, [{"echo_n", {ssh_echo_server, [10]}}]}]). {ok,<0.54.0>} 3> </code> @@ -293,6 +382,7 @@ terminate(_Reason, _State) -> {ssh_msg, <0.57.0>, {closed, 0}} 7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity). </code> +<p>See also <seealso marker="ssh_channel"> ssh_channel(3)</seealso>.</p> </section> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 51ad691ba2..d4b02a024e 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -347,6 +347,8 @@ handle_option([parallel_login|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]); handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -439,6 +441,10 @@ handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> Opt; +handle_ssh_option({id_string, random}) -> + {id_string, {random,2,5}}; %% 2 - 5 random characters +handle_ssh_option({id_string, ID} = Opt) when is_list(ID) -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 76fa776113..8669be570e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,12 +44,34 @@ versions(client, Options)-> Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION), - Version = format_version(Vsn), - {Vsn, Version}; + {Vsn, format_version(Vsn, software_version(Options))}; versions(server, Options) -> Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION), - Version = format_version(Vsn), - {Vsn, Version}. + {Vsn, format_version(Vsn, software_version(Options))}. + +software_version(Options) -> + case proplists:get_value(id_string, Options) of + undefined -> + "Erlang"++ssh_vsn(); + {random,Nlo,Nup} -> + random_id(Nlo,Nup); + ID -> + ID + end. + +ssh_vsn() -> + try {ok,L} = application:get_all_key(ssh), + proplists:get_value(vsn,L,"") + of + "" -> ""; + VSN when is_list(VSN) -> "/" ++ VSN; + _ -> "" + catch + _:_ -> "" + end. + +random_id(Nlo, Nup) -> + [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ]. hello_version_msg(Data) -> [Data,"\r\n"]. @@ -77,9 +99,9 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm, yes_no(Ssh, Prompt) -> (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). -format_version({Major,Minor}) -> +format_version({Major,Minor}, SoftwareVersion) -> "SSH-" ++ integer_to_list(Major) ++ "." ++ - integer_to_list(Minor) ++ "-Erlang". + integer_to_list(Minor) ++ "-" ++ SoftwareVersion. handle_hello_version(Version) -> try diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 3331038450..bd029ad420 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -52,6 +52,12 @@ all() -> ssh_connect_arg4_timeout, packet_size_zero, ssh_daemon_minimal_remote_max_packet_size_option, + id_string_no_opt_client, + id_string_own_string_client, + id_string_random_client, + id_string_no_opt_server, + id_string_own_string_server, + id_string_random_server, {group, hardening_tests} ]. @@ -816,6 +822,66 @@ ssh_daemon_minimal_remote_max_packet_size_option(Config) -> ssh:stop_daemon(Server). %%-------------------------------------------------------------------- +id_string_no_opt_client(Config) -> + {Server, Host, Port} = fake_daemon(Config), + {error,_} = ssh:connect(Host, Port, []), + receive + {id,Server,"SSH-2.0-Erlang/"++Vsn} -> + true = expected_ssh_vsn(Vsn); + {id,Server,Other} -> + ct:fail("Unexpected id: ~s.",[Other]) + end. + +%%-------------------------------------------------------------------- +id_string_own_string_client(Config) -> + {Server, Host, Port} = fake_daemon(Config), + {error,_} = ssh:connect(Host, Port, [{id_string,"Pelle"}]), + receive + {id,Server,"SSH-2.0-Pelle\r\n"} -> + ok; + {id,Server,Other} -> + ct:fail("Unexpected id: ~s.",[Other]) + end. + +%%-------------------------------------------------------------------- +id_string_random_client(Config) -> + {Server, Host, Port} = fake_daemon(Config), + {error,_} = ssh:connect(Host, Port, [{id_string,random}]), + receive + {id,Server,Id="SSH-2.0-Erlang"++_} -> + ct:fail("Unexpected id: ~s.",[Id]); + {id,Server,Rnd="SSH-2.0-"++_} -> + ct:log("Got ~s.",[Rnd]); + {id,Server,Id} -> + ct:fail("Unexpected id: ~s.",[Id]) + end. + +%%-------------------------------------------------------------------- +id_string_no_opt_server(Config) -> + {_Server, Host, Port} = std_daemon(Config, []), + {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]), + {ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000), + true = expected_ssh_vsn(Vsn). + +%%-------------------------------------------------------------------- +id_string_own_string_server(Config) -> + {_Server, Host, Port} = std_daemon(Config, [{id_string,"Olle"}]), + {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]), + {ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000). + +%%-------------------------------------------------------------------- +id_string_random_server(Config) -> + {_Server, Host, Port} = std_daemon(Config, [{id_string,random}]), + {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]), + {ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000), + case Rnd of + "Erlang"++_ -> ct:log("Id=~p",[Rnd]), + {fail,got_default_id}; + "Olle\r\n" -> {fail,got_previous_tests_value}; + _ -> ct:log("Got ~s.",[Rnd]) + end. + +%%-------------------------------------------------------------------- ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true). ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false). @@ -1094,3 +1160,46 @@ do_shell(IO, Shell) -> %% {'EXIT', Shell, killed} -> %% ok %% end. + + +std_daemon(Config, ExtraOpts) -> + SystemDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + {_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2} | ExtraOpts]). + +expected_ssh_vsn(Str) -> + try + {ok,L} = application:get_all_key(ssh), + proplists:get_value(vsn,L,"")++"\r\n" + of + Str -> true; + "\r\n" -> true; + _ -> false + catch + _:_ -> true %% ssh not started so we dont't know + end. + + +fake_daemon(_Config) -> + Parent = self(), + %% start the server + Server = spawn(fun() -> + {ok,Sl} = gen_tcp:listen(0,[]), + {ok,{Host,Port}} = inet:sockname(Sl), + Parent ! {sockname,self(),Host,Port}, + Rsa = gen_tcp:accept(Sl), + ct:log("Server gen_tcp:accept got ~p",[Rsa]), + {ok,S} = Rsa, + receive + {tcp, S, Id} -> Parent ! {id,self(),Id} + end + end), + %% Get listening host and port + receive + {sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort} + end. + diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index fec8dacab7..b2b85a717f 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,4 +1,4 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 3.2.1 +SSH_VSN = 3.2.2 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 955875fa95..be8ef6f85f 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -53,7 +53,7 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-2.0","public_key-0.22","kernel-3.0", - "erts-6.0","crypto-3.3"]}]}. + {runtime_dependencies, ["stdlib-2.0","public_key-1.0","kernel-3.0", + "erts-6.0","crypto-3.3", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 493e5a87d9..b538fefe53 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -243,7 +243,7 @@ key_exchange(client, _Version, {dh, PublicKey}) -> dh_public = PublicKey} }; -key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}}}) -> +key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> #client_key_exchange{ exchange_keys = #client_ec_diffie_hellman_public{ dh_public = ECPublicKey} @@ -284,7 +284,7 @@ key_exchange(server, Version, {dh, {PublicKey, _}, enc_server_key_exchange(Version, ServerDHParams, HashSign, ClientRandom, ServerRandom, PrivateKey); -key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}, +key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey, parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, @@ -578,11 +578,10 @@ prf({3,_N}, Secret, Label, Seed, WantedLength) -> %%-------------------------------------------------------------------- select_hashsign(_, undefined, _Version) -> {null, anon}; -select_hashsign(undefined, Cert, Version) -> - #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - select_hashsign_algs(undefined, Algo, Version); -select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, Version) -> +%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have +%% negotiated a lower version. +select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, {Major, Minor} = Version) + when Major >= 3 andalso Minor >= 3 -> #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, DefaultHashSign = {_, Sign} = select_hashsign_algs(undefined, Algo, Version), @@ -600,7 +599,11 @@ select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, Version) -> DefaultHashSign; [HashSign| _] -> HashSign - end. + end; +select_hashsign(_, Cert, Version) -> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + select_hashsign_algs(undefined, Algo, Version). %%-------------------------------------------------------------------- -spec select_hashsign_algs(#hash_sign_algos{}| undefined, oid(), ssl_record:ssl_version()) -> diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl index b534c0130e..12ad1e5402 100644 --- a/lib/ssl/test/erl_make_certs.erl +++ b/lib/ssl/test/erl_make_certs.erl @@ -114,7 +114,7 @@ verify_signature(DerEncodedCert, DerKey, _KeyParams) -> #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, - parameters = Params, publicKey = {0, PubKey}} -> + parameters = Params, publicKey = PubKey} -> public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) end. @@ -292,7 +292,7 @@ publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> publickey(#'ECPrivateKey'{version = _Version, privateKey = _PrivKey, parameters = Params, - publicKey = {0, PubKey}}) -> + publicKey = PubKey}) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = #'ECPoint'{point = PubKey}}. @@ -401,9 +401,9 @@ gen_ec2(CurveId) -> {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), #'ECPrivateKey'{version = 1, - privateKey = binary_to_list(PrivKey), + privateKey = PrivKey, parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, - publicKey = {0, PubKey}}. + publicKey = PubKey}. %% See fips_186-3.pdf dsa_search(T, P0, Q, Iter) when Iter > 0 -> diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 8dca733526..d4433393a1 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2014. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. 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 @@ -40,7 +40,47 @@ all() -> [decode_hello_handshake, encode_single_hello_sni_extension_correctly, decode_single_hello_sni_extension_correctly, decode_empty_server_sni_correctly, - select_proper_tls_1_2_rsa_default_hashsign]. + select_proper_tls_1_2_rsa_default_hashsign, + ignore_hassign_extension_pre_tls_1_2]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. +end_per_suite(Config) -> + Config. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_,Config) -> + Config. + +init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case is_supported(sha512) of + true -> + ssl:start(), + %% make rsa certs using oppenssl + Result = + (catch make_certs:all(?config(data_dir, Config0), + ?config(priv_dir, Config0))), + ct:log("Make certs ~p~n", [Result]), + ssl_test_lib:cert_options(Config0); + false -> + {skip, "Crypto did not support sha512"} + end + catch _:_ -> + {skip, "Crypto did not start"} + end; +init_per_testcase(_, Config0) -> + Config0. + +end_per_testcase(ignore_hassign_extension_pre_tls_1_2, _) -> + crypto:stop(); +end_per_testcase(_TestCase, Config) -> + Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- @@ -121,3 +161,18 @@ select_proper_tls_1_2_rsa_default_hashsign(_Config) -> {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,2}), {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,0}). + +ignore_hassign_extension_pre_tls_1_2(Config) -> + Opts = ?config(server_opts, Config), + CertFile = proplists:get_value(certfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile), + HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}]}, + {sha512, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,3}), + %%% Ignore + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,2}), + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, {3,0}). + +is_supported(Hash) -> + Algos = crypto:supports(), + Hashs = proplists:get_value(hashs, Algos), + lists:member(Hash, Hashs). diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 27ee07ffc6..94426a3061 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. 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 @@ -291,7 +291,7 @@ basic_erlang_server_openssl_client(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ - " -host localhost" ++ workaround_openssl_s_client(), + " -host localhost" ++ workaround_openssl_s_clinent(), ct:log("openssl cmd: ~p~n", [Cmd]), @@ -1658,7 +1658,7 @@ supports_sslv2(Port) -> true end. -workaround_openssl_s_client() -> +workaround_openssl_s_clinent() -> %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 %% https://bugs.archlinux.org/task/33919 %% Bug seems to manifests it self if TLS version is not @@ -1672,8 +1672,6 @@ workaround_openssl_s_client() -> " -no_tls1_2 "; "OpenSSL 1.0.1f" ++ _ -> " -no_tls1_2 "; - "OpenSSL 1.0.1l" ++ _ -> - " -cipher AES256-SHA"; - _ -> + _ -> "" end. diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index a915e567a5..3c92de59b9 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -321,7 +321,7 @@ gen_server:abcast -----> Module:handle_cast/2 which may be infinity.</p> <p>This problem does not exist if all nodes are Erlang nodes.</p> </warning> - <p>To avoid that late answers (after the timeout) pollutes + <p>To prevent late answers (after the timeout) from polluting the caller's message queue, a middleman process is used to do the actual calls. Late answers will then be discarded when they arrive to a terminated process.</p> diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml index f766c843be..59c26d9896 100644 --- a/lib/stdlib/doc/src/maps.xml +++ b/lib/stdlib/doc/src/maps.xml @@ -40,6 +40,9 @@ Returns a tuple <c>{ok, Value}</c> where <c><anno>Value</anno></c> is the value associated with <c><anno>Key</anno></c>, or <c>error</c> if no value is associated with <c><anno>Key</anno></c> in <c><anno>Map</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map = #{"hi" => 42}, @@ -95,8 +98,10 @@ <p> Returns the value <c><anno>Value</anno></c> associated with <c><anno>Key</anno></c> if <c><anno>Map</anno></c> contains <c><anno>Key</anno></c>. - If no value is associated with <c><anno>Key</anno></c> then the call will - fail with an exception. + </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map, + or with a <c>{badkey,Key}</c> exception if no value is associated with <c><anno>Key</anno></c>. </p> <p>Example:</p> <code type="none"> @@ -116,6 +121,10 @@ <c><anno>Map</anno></c> contains <c><anno>Key</anno></c>. If no value is associated with <c><anno>Key</anno></c> then returns <c><anno>Default</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. + + </p> <p>Example:</p> <code type="none"> > Map = #{ key1 => val1, key2 => val2 }. @@ -134,7 +143,9 @@ val1 <p> Returns <c>true</c> if map <c><anno>Map</anno></c> contains <c><anno>Key</anno></c> and returns <c>false</c> if it does not contain the <c><anno>Key</anno></c>. - The function will fail with an exception if <c><anno>Map</anno></c> is not a Map. + </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. </p> <p>Example:</p> <code type="none"> @@ -154,6 +165,9 @@ false</code> <p> Returns a complete list of keys, in arbitrary order, which resides within <c><anno>Map</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map = #{42 => value_three,1337 => "value two","a" => 1}, @@ -189,6 +203,10 @@ false</code> Merges two maps into a single map <c><anno>Map3</anno></c>. If two keys exists in both maps the value in <c><anno>Map1</anno></c> will be superseded by the value in <c><anno>Map2</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map1</anno></c> or + <c><anno>Map2</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map1 = #{a => "value_one", b => "value_two"}, @@ -222,6 +240,10 @@ false</code> replaced by value <c><anno>Value</anno></c>. The function returns a new map <c><anno>Map2</anno></c> containing the new association and the old associations in <c><anno>Map1</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map1</anno></c> is not a map. + </p> + <p>Example:</p> <code type="none"> > Map = #{"a" => 1}. @@ -241,6 +263,9 @@ false</code> The function removes the <c><anno>Key</anno></c>, if it exists, and its associated value from <c><anno>Map1</anno></c> and returns a new map <c><anno>Map2</anno></c> without key <c><anno>Key</anno></c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map1</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map = #{"a" => 1}. @@ -276,6 +301,9 @@ false</code> The fuction returns a list of pairs representing the key-value associations of <c><anno>Map</anno></c>, where the pairs, <c>[{K1,V1}, ..., {Kn,Vn}]</c>, are returned in arbitrary order. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map = #{42 => value_three,1337 => "value two","a" => 1}, @@ -291,8 +319,11 @@ false</code> <p> If <c><anno>Key</anno></c> exists in <c><anno>Map1</anno></c> the old associated value is replaced by value <c><anno>Value</anno></c>. The function returns a new map <c><anno>Map2</anno></c> containing - the new associated value. If <c><anno>Key</anno></c> does not exist in <c><anno>Map1</anno></c> an exception is - generated. + the new associated value. + </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map1</anno></c> is not a map, + or with a <c>{badkey,Key}</c> exception if no value is associated with <c><anno>Key</anno></c>. </p> <p>Example:</p> <code type="none"> @@ -310,6 +341,9 @@ false</code> <p> Returns a complete list of values, in arbitrary order, contained in map <c>M</c>. </p> + <p> + The call will fail with a <c>{badmap,Map}</c> exception if <c><anno>Map</anno></c> is not a map. + </p> <p>Example:</p> <code type="none"> > Map = #{42 => value_three,1337 => "value two","a" => 1}, diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl index 371573dc23..e86e10b170 100644 --- a/lib/stdlib/src/erl_eval.erl +++ b/lib/stdlib/src/erl_eval.erl @@ -246,18 +246,14 @@ expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> %% map expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) -> {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none), - case Map0 of - #{} -> - {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef), - Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) -> - maps:put(K, V, Mi); - ({map_exact,K,V}, Mi) -> - maps:update(K, V, Mi) - end, Map0, Vs), - ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs); - _ -> - erlang:raise(error, {badarg,Map0}, stacktrace()) - end; + {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef), + _ = maps:put(k, v, Map0), %Validate map. + Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) -> + maps:put(K, V, Mi); + ({map_exact,K,V}, Mi) -> + maps:update(K, V, Mi) + end, Map0, Vs), + ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs); expr({map,_,Es}, Bs0, Lf, Ef, RBs) -> {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef), ret_expr(lists:foldl(fun diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 64a00acd88..dc74d611a3 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -384,21 +384,11 @@ expr({call,Line,{tuple,_,[{atom,_,erlang},{atom,_,is_record}]}, expr({call,Line,{atom,_La,N}=Atom,As0}, St0) -> {As,St1} = expr_list(As0, St0), Ar = length(As), - case erl_internal:bif(N, Ar) of - true -> - {{call,Line,Atom,As},St1}; - false -> - case imported(N, Ar, St1) of - {yes,_Mod} -> - {{call,Line,Atom,As},St1}; - no -> - case {N,Ar} of - {record_info,2} -> - record_info_call(Line, As, St1); - _ -> - {{call,Line,Atom,As},St1} - end - end + case {N,Ar} =:= {record_info,2} andalso not imported(N, Ar, St1) of + true -> + record_info_call(Line, As, St1); + false -> + {{call,Line,Atom,As},St1} end; expr({call,Line,{remote,Lr,M,F},As0}, St0) -> {[M1,F1 | As1],St1} = expr_list([M,F | As0], St0), @@ -832,10 +822,7 @@ add_imports(Mod, [F | Fs], Is) -> add_imports(_, [], Is) -> Is. imported(F, A, St) -> - case orddict:find({F,A}, St#exprec.imports) of - {ok,Mod} -> {yes,Mod}; - error -> no - end. + orddict:is_key({F,A}, St#exprec.imports). %%% %%% Replace is_record/3 in guards with matching if possible. diff --git a/lib/stdlib/src/gb_sets.erl b/lib/stdlib/src/gb_sets.erl index 0a26d0182d..393fb07229 100644 --- a/lib/stdlib/src/gb_sets.erl +++ b/lib/stdlib/src/gb_sets.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2014. All Rights Reserved. +%% Copyright Ericsson AB 2001-2015. 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 @@ -207,21 +207,19 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% gb_sets:set() in OTP 17 only. - -spec empty() -> Set when - Set :: gb_sets:set(). + Set :: set(). empty() -> {0, nil}. -spec new() -> Set when - Set :: gb_sets:set(). + Set :: set(). new() -> empty(). -spec is_empty(Set) -> boolean() when - Set :: gb_sets:set(). + Set :: set(). is_empty({0, nil}) -> true; @@ -229,7 +227,7 @@ is_empty(_) -> false. -spec size(Set) -> non_neg_integer() when - Set :: gb_sets:set(). + Set :: set(). size({Size, _}) -> Size. diff --git a/lib/stdlib/src/orddict.erl b/lib/stdlib/src/orddict.erl index c98d78b34d..af5d917840 100644 --- a/lib/stdlib/src/orddict.erl +++ b/lib/stdlib/src/orddict.erl @@ -115,8 +115,8 @@ erase(_, []) -> []. Orddict1 :: orddict(), Orddict2 :: orddict(). -store(Key, New, [{K,_}=E|Dict]) when Key < K -> - [{Key,New},E|Dict]; +store(Key, New, [{K,_}|_]=Dict) when Key < K -> + [{Key,New}|Dict]; store(Key, New, [{K,_}=E|Dict]) when Key > K -> [E|store(Key, New, Dict)]; store(Key, New, [{_K,_Old}|Dict]) -> %Key == K @@ -129,8 +129,8 @@ store(Key, New, []) -> [{Key,New}]. Orddict1 :: orddict(), Orddict2 :: orddict(). -append(Key, New, [{K,_}=E|Dict]) when Key < K -> - [{Key,[New]},E|Dict]; +append(Key, New, [{K,_}|_]=Dict) when Key < K -> + [{Key,[New]}|Dict]; append(Key, New, [{K,_}=E|Dict]) when Key > K -> [E|append(Key, New, Dict)]; append(Key, New, [{_K,Old}|Dict]) -> %Key == K @@ -143,8 +143,8 @@ append(Key, New, []) -> [{Key,[New]}]. Orddict1 :: orddict(), Orddict2 :: orddict(). -append_list(Key, NewList, [{K,_}=E|Dict]) when Key < K -> - [{Key,NewList},E|Dict]; +append_list(Key, NewList, [{K,_}|_]=Dict) when Key < K -> + [{Key,NewList}|Dict]; append_list(Key, NewList, [{K,_}=E|Dict]) when Key > K -> [E|append_list(Key, NewList, Dict)]; append_list(Key, NewList, [{_K,Old}|Dict]) -> %Key == K @@ -170,8 +170,8 @@ update(Key, Fun, [{K,Val}|Dict]) when Key == K -> Orddict1 :: orddict(), Orddict2 :: orddict(). -update(Key, _, Init, [{K,_}=E|Dict]) when Key < K -> - [{Key,Init},E|Dict]; +update(Key, _, Init, [{K,_}|_]=Dict) when Key < K -> + [{Key,Init}|Dict]; update(Key, Fun, Init, [{K,_}=E|Dict]) when Key > K -> [E|update(Key, Fun, Init, Dict)]; update(Key, Fun, _Init, [{_K,Val}|Dict]) -> %Key == K @@ -184,8 +184,8 @@ update(Key, _, Init, []) -> [{Key,Init}]. Orddict1 :: orddict(), Orddict2 :: orddict(). -update_counter(Key, Incr, [{K,_}=E|Dict]) when Key < K -> - [{Key,Incr},E|Dict]; +update_counter(Key, Incr, [{K,_}|_]=Dict) when Key < K -> + [{Key,Incr}|Dict]; update_counter(Key, Incr, [{K,_}=E|Dict]) when Key > K -> [E|update_counter(Key, Incr, Dict)]; update_counter(Key, Incr, [{_K,Val}|Dict]) -> %Key == K diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 540c1cac9c..0fb6974426 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -596,7 +596,7 @@ obsolete_1(core_lib, is_literal_list, 1) -> obsolete_1(core_lib, literal_value, 1) -> {deprecated,{core_lib,concrete,1}}; obsolete_1(ssl, negotiated_next_protocol, 1) -> - {deprecated,{ssl,negotiated_protocol}}; + {deprecated,{ssl,negotiated_protocol,1}}; obsolete_1(_, _, _) -> no. diff --git a/lib/stdlib/src/slave.erl b/lib/stdlib/src/slave.erl index 1898dc8aba..28da45621a 100644 --- a/lib/stdlib/src/slave.erl +++ b/lib/stdlib/src/slave.erl @@ -128,7 +128,7 @@ relay1(Pid) -> %% {error, {already_running, Name@Host}} -spec start(Host) -> {ok, Node} | {error, Reason} when - Host :: atom(), + Host :: inet:hostname(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -138,8 +138,8 @@ start(Host) -> start(Host, Name, [], no_link). -spec start(Host, Name) -> {ok, Node} | {error, Reason} when - Host :: atom(), - Name :: atom(), + Host :: inet:hostname(), + Name :: atom() | string(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -147,8 +147,8 @@ start(Host, Name) -> start(Host, Name, []). -spec start(Host, Name, Args) -> {ok, Node} | {error, Reason} when - Host :: atom(), - Name :: atom(), + Host :: inet:hostname(), + Name :: atom() | string(), Args :: string(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -157,7 +157,7 @@ start(Host, Name, Args) -> start(Host, Name, Args, no_link). -spec start_link(Host) -> {ok, Node} | {error, Reason} when - Host :: atom(), + Host :: inet:hostname(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -167,8 +167,8 @@ start_link(Host) -> start(Host, Name, [], self()). -spec start_link(Host, Name) -> {ok, Node} | {error, Reason} when - Host :: atom(), - Name :: atom(), + Host :: inet:hostname(), + Name :: atom() | string(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -176,8 +176,8 @@ start_link(Host, Name) -> start_link(Host, Name, []). -spec start_link(Host, Name, Args) -> {ok, Node} | {error, Reason} when - Host :: atom(), - Name :: atom(), + Host :: inet:hostname(), + Name :: atom() | string(), Args :: string(), Node :: node(), Reason :: timeout | no_rsh | {already_running, Node}. @@ -210,7 +210,6 @@ start(Host0, Name, Args, LinkTo, Prog) -> Node :: node(). stop(Node) -> -% io:format("stop(~p)~n", [Node]), rpc:call(Node, erlang, halt, []), ok. @@ -229,7 +228,6 @@ wait_for_slave(Parent, Host, Name, Node, Args, LinkTo, Prog) -> Waiter = register_unique_name(0), case mk_cmd(Host, Name, Args, Waiter, Prog) of {ok, Cmd} -> -%% io:format("Command: ~ts~n", [Cmd]), open_port({spawn, Cmd}, [stream]), receive {SlavePid, slave_started} -> diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl index 7c0cd8b26a..67655b1145 100644 --- a/lib/stdlib/src/supervisor.erl +++ b/lib/stdlib/src/supervisor.erl @@ -1403,13 +1403,8 @@ add_restart([R|Restarts], Now, Period) -> add_restart([], _, _) -> []. -inPeriod(Time, Now, Period) -> - case Time - Now of - T when T > Period -> - false; - _ -> - true - end. +inPeriod(Then, Now, Period) -> + Now =< Then + Period. %%% ------------------------------------------------------ %%% Error and progress reporting. diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl index 19d803345e..c266177b4d 100644 --- a/lib/stdlib/src/timer.erl +++ b/lib/stdlib/src/timer.erl @@ -161,10 +161,11 @@ sleep(T) -> Time :: integer(), Value :: term(). tc(F) -> - Before = os:timestamp(), + T1 = erlang:monotonic_time(), Val = F(), - After = os:timestamp(), - {now_diff(After, Before), Val}. + T2 = erlang:monotonic_time(), + Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), + {Time, Val}. %% %% Measure the execution time (in microseconds) for Fun(Args). @@ -175,10 +176,11 @@ tc(F) -> Time :: integer(), Value :: term(). tc(F, A) -> - Before = os:timestamp(), + T1 = erlang:monotonic_time(), Val = apply(F, A), - After = os:timestamp(), - {now_diff(After, Before), Val}. + T2 = erlang:monotonic_time(), + Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), + {Time, Val}. %% %% Measure the execution time (in microseconds) for an MFA. @@ -190,10 +192,11 @@ tc(F, A) -> Time :: integer(), Value :: term(). tc(M, F, A) -> - Before = os:timestamp(), + T1 = erlang:monotonic_time(), Val = apply(M, F, A), - After = os:timestamp(), - {now_diff(After, Before), Val}. + T2 = erlang:monotonic_time(), + Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), + {Time, Val}. %% %% Calculate the time difference (in microseconds) of two diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl index 44e75ff15b..3c67bd67c6 100644 --- a/lib/stdlib/src/zip.erl +++ b/lib/stdlib/src/zip.erl @@ -1150,7 +1150,7 @@ server_loop(Parent, OpenZip) -> From ! {self(), OpenZip}, server_loop(Parent, OpenZip); {'EXIT', Parent, Reason} -> - openzip_close(OpenZip), + _ = openzip_close(OpenZip), exit({parent_died, Reason}); _ -> {error, bad_msg} diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index 3427f431c5..a750c5cace 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -1482,8 +1482,11 @@ eep43(Config) when is_list(Config) -> " #{ K1 := 1, K2 := 2, K3 := 3, {2,2} := 4} = Map " "end.", #{ 1 => 1, <<42:301>> => 2, {3,<<42:301>>} => 3, {2,2} => 4}), - error_check("[camembert]#{}.", {badarg,[camembert]}), + error_check("[camembert]#{}.", {badmap,[camembert]}), + error_check("[camembert]#{nonexisting:=v}.", {badmap,[camembert]}), error_check("#{} = 1.", {badmatch,1}), + error_check("[]#{a=>error(bad)}.", bad), + error_check("(#{})#{nonexisting:=value}.", {badkey,nonexisting}), ok. %% Check the string in different contexts: as is; in fun; from compiled code. diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl index c98654aef7..9dcf19707c 100644 --- a/lib/stdlib/test/supervisor_SUITE.erl +++ b/lib/stdlib/test/supervisor_SUITE.erl @@ -53,7 +53,8 @@ temporary_abnormal/1, temporary_bystander/1]). %% Restart strategy tests --export([ one_for_one/1, +-export([ multiple_restarts/1, + one_for_one/1, one_for_one_escalation/1, one_for_all/1, one_for_all_escalation/1, one_for_all_other_child_fails_restart/1, simple_one_for_one/1, simple_one_for_one_escalation/1, @@ -78,6 +79,7 @@ suite() -> all() -> [{group, sup_start}, {group, sup_start_map}, {group, sup_stop}, child_adm, child_adm_simple, extra_return, child_specs, sup_flags, + multiple_restarts, {group, restart_one_for_one}, {group, restart_one_for_all}, {group, restart_simple_one_for_one}, @@ -873,6 +875,39 @@ temporary_bystander(_Config) -> [{child1, _, _, _}] = supervisor:which_children(SupPid2). %%------------------------------------------------------------------------- +%% Test restarting a process multiple times, being careful not +%% to exceed the maximum restart frquency. +multiple_restarts(Config) when is_list(Config) -> + process_flag(trap_exit, true), + Child1 = #{id => child1, + start => {supervisor_1, start_child, []}, + restart => permanent, + shutdown => brutal_kill, + type => worker, + modules => []}, + SupFlags = #{strategy => one_for_one, + intensity => 1, + period => 1}, + {ok, SupPid} = start_link({ok, {SupFlags, []}}), + {ok, CPid1} = supervisor:start_child(sup_test, Child1), + + %% Terminate the process several times, but being careful + %% not to exceed the maximum restart intensity. + terminate(SupPid, CPid1, child1, abnormal), + _ = [begin + receive after 2100 -> ok end, + [{_, Pid, _, _}|_] = supervisor:which_children(sup_test), + terminate(SupPid, Pid, child1, abnormal) + end || _ <- [1,2,3]], + + %% Verify that the supervisor is still alive and clean up. + ok = supervisor:terminate_child(SupPid, child1), + ok = supervisor:delete_child(SupPid, child1), + exit(SupPid, kill), + ok. + + +%%------------------------------------------------------------------------- %% Test the one_for_one base case. one_for_one(Config) when is_list(Config) -> process_flag(trap_exit, true), diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index acd2e0bff2..7f2da7755a 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -1822,7 +1822,7 @@ time_ms_check(Other) -> time_ms_apply(Func, TCPid, MultAndScale) -> {_,GL} = process_info(TCPid, group_leader), WhoAmI = self(), % either TC or IO server - T0 = os:timestamp(), + T0 = erlang:monotonic_time(), UserTTSup = spawn(fun() -> user_timetrap_supervisor(Func, WhoAmI, TCPid, @@ -1855,7 +1855,8 @@ user_timetrap_supervisor(Func, Spawner, TCPid, GL, T0, MultAndScale) -> receive {UserTT,Result} -> demonitor(MonRef, [flush]), - Elapsed = trunc(timer:now_diff(os:timestamp(), T0) / 1000), + T1 = erlang:monotonic_time(), + Elapsed = erlang:convert_time_unit(T1-T0, native, milli_seconds), try time_ms_check(Result) of TimeVal -> %% this is the new timetrap value to set (return value @@ -1923,7 +1924,7 @@ update_user_timetraps(TCPid, StartTime) -> proplists:delete(TCPid, UserTTs)), proceed; {OtherUserTTSup,OtherStartTime} -> - case timer:now_diff(OtherStartTime, StartTime) of + case OtherStartTime - StartTime of Diff when Diff >= 0 -> ignore; _ -> diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index 0c003bab39..3610356355 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -2444,7 +2444,10 @@ This is automagically called by the user level function `indent-region'." ;; Parse the Erlang code from the beginning of the clause to ;; the beginning of the region. (while (< (point) indent-point) - (setq state (erlang-partial-parse (point) indent-point state))) + (let ((pt (point))) + (setq state (erlang-partial-parse pt indent-point state)) + (if (= pt (point)) + (error "Illegal syntax")))) ;; Indent every line in the region (while continue (goto-char indent-point) @@ -2480,8 +2483,11 @@ This is automagically called by the user level function `indent-region'." (if (>= from-end (- (point-max) indent-point)) (setq continue nil) (while (< (point) indent-point) - (setq state (erlang-partial-parse - (point) indent-point state)))))))) + (let ((pt (point))) + (setq state (erlang-partial-parse + pt indent-point state)) + (if (= pt (point)) + (error "Illegal syntax"))))))))) (defun erlang-indent-current-buffer () @@ -2528,7 +2534,10 @@ Return nil if line starts inside string, t if in a comment." (goto-char parse-start) (erlang-beginning-of-clause)) (while (< (point) indent-point) - (setq state (erlang-partial-parse (point) indent-point state))) + (let ((pt (point))) + (setq state (erlang-partial-parse pt indent-point state)) + (if (= pt (point)) + (error "Illegal syntax")))) (erlang-calculate-stack-indent indent-point state)))) (defun erlang-show-syntactic-information () @@ -2698,12 +2707,13 @@ Value is list (stack token-start token-type in-what)." (erlang-push (list '|| token (current-column)) stack) (forward-char 2)) - ;; Bit-syntax open paren - ((looking-at "<<") + ;; Bit-syntax open. Note that map syntax allows "<<" to follow ":=" + ;; or "=>" without intervening whitespace, so handle that case here + ((looking-at "\\(:=\\|=>\\)?<<") (erlang-push (list '<< token (current-column)) stack) - (forward-char 2)) + (forward-char (- (match-end 0) (match-beginning 0)))) - ;; Bbit-syntax close paren + ;; Bit-syntax close ((looking-at ">>") (while (memq (car (car stack)) '(|| ->)) (erlang-pop stack)) @@ -4188,7 +4198,10 @@ This function is designed to be a member of a criteria list." ;; Do not return `stop' when inside a list comprehension ;; construction. (The point must be after `||'). (while (< (point) orig-point) - (setq state (erlang-partial-parse (point) orig-point state))) + (let ((pt (point))) + (setq state (erlang-partial-parse pt orig-point state)) + (if (= pt (point)) + (error "Illegal syntax")))) (if (and (car state) (eq (car (car (car state))) '||)) nil 'stop))) diff --git a/lib/tools/emacs/test.erl.indented b/lib/tools/emacs/test.erl.indented index 1c1086ca58..abb05fd59b 100644 --- a/lib/tools/emacs/test.erl.indented +++ b/lib/tools/emacs/test.erl.indented @@ -32,6 +32,14 @@ -module(test). -compile(export_all). +%% Used to cause an "Unbalanced parentheses" error. +foo(M) -> + M#{a :=<<"a">> + ,b:=1}. +foo() -> + #{a =><<"a">> + ,b=>1}. + %% Module attributes should be highlighted -export([t/1]). diff --git a/lib/tools/emacs/test.erl.orig b/lib/tools/emacs/test.erl.orig index a9d09000d2..3d8f29fe18 100644 --- a/lib/tools/emacs/test.erl.orig +++ b/lib/tools/emacs/test.erl.orig @@ -32,6 +32,14 @@ -module(test). -compile(export_all). +%% Used to cause an "Unbalanced parentheses" error. +foo(M) -> +M#{a :=<<"a">> +,b:=1}. +foo() -> +#{a =><<"a">> +,b=>1}. + %% Module attributes should be highlighted -export([t/1]). diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl index a3fef91e61..28cf493a5f 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -485,20 +485,22 @@ string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpC erlang:max(TpCW, length(Stpc)) }, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]). -print_bp_mfa(Mfas, {_Tn, Tus}, Fd, Opts) -> +print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts) -> Fmfas = filter_mfa(sort_mfa(Mfas, proplists:get_value(sort, Opts)), proplists:get_value(filter, Opts)), {{MfaW, CountW, PercW, TimeW, TpCW}, Strs} = string_bp_mfa(Fmfas, Tus), - Ws = { - erlang:max(length("FUNCTION"), MfaW), - erlang:max(length("CALLS"), CountW), - erlang:max(length(" %"), PercW), - erlang:max(length("TIME"), TimeW), - erlang:max(length("uS / CALLS"), TpCW) - }, - format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]), - format(Fd, Ws, ["--------", "-----", "---", "----", "----------"]), - + TnStr = s(Tn), + TusStr = s(Tus), + TuspcStr = s("~.2f", [divide(Tus,Tn)]), + Ws = {erlang:max(length("FUNCTION"), MfaW), + lists:max([length("CALLS"), CountW, length(TnStr)]), + erlang:max(length(" %"), PercW), + lists:max([length("TIME"), TimeW, length(TusStr)]), + lists:max([length("uS / CALLS"), TpCW, length(TuspcStr)])}, + format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]), + format(Fd, Ws, ["--------", "-----", "-------", "----", "----------"]), lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs), + format(Fd, Ws, [lists:duplicate(N,$-)||N <- tuple_to_list(Ws)]), + format(Fd, Ws, ["Total:", TnStr, "100.00%", TusStr, TuspcStr]), ok. s({M,F,A}) -> s("~w:~w/~w",[M,F,A]); |