aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/compiler/src/beam_clean.erl2
-rw-r--r--lib/compiler/src/cerl_inline.erl17
-rw-r--r--lib/compiler/src/cerl_trees.erl109
-rw-r--r--lib/compiler/src/sys_core_fold.erl22
-rw-r--r--lib/compiler/src/v3_codegen.erl5
-rw-r--r--lib/compiler/src/v3_core.erl12
-rw-r--r--lib/compiler/src/v3_kernel.erl11
-rw-r--r--lib/compiler/test/lc_SUITE.erl33
-rw-r--r--lib/crypto/src/crypto.erl9
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/fun_arity32
-rw-r--r--lib/kernel/doc/src/seq_trace.xml9
-rw-r--r--lib/kernel/include/dist.hrl1
-rw-r--r--lib/kernel/src/dist_util.erl2
-rw-r--r--lib/kernel/src/file.erl29
-rw-r--r--lib/kernel/src/inet.erl15
-rw-r--r--lib/kernel/src/seq_trace.erl6
-rw-r--r--lib/kernel/test/file_SUITE.erl6
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl6
-rw-r--r--lib/kernel/test/seq_trace_SUITE.erl109
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml2
-rw-r--r--lib/ssh/doc/src/ssh.xml93
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml6
-rw-r--r--lib/ssh/src/ssh_cli.erl270
-rw-r--r--lib/ssh/src/ssh_options.erl8
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl104
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml2
-rw-r--r--lib/stdlib/doc/src/ordsets.xml9
-rw-r--r--lib/stdlib/doc/src/sets.xml9
-rw-r--r--lib/stdlib/src/erl_posix_msg.erl7
-rw-r--r--lib/stdlib/src/ordsets.erl9
-rw-r--r--lib/stdlib/src/sets.erl8
-rw-r--r--lib/stdlib/test/sets_SUITE.erl15
-rw-r--r--lib/stdlib/test/sets_test_lib.erl13
-rw-r--r--lib/tools/emacs/erlang.el1
-rw-r--r--lib/xmerl/src/xmerl_xsd.erl2
35 files changed, 767 insertions, 226 deletions
diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl
index 7ddf9fa2e2..955c128699 100644
--- a/lib/compiler/src/beam_clean.erl
+++ b/lib/compiler/src/beam_clean.erl
@@ -254,7 +254,7 @@ bs_restores([_|Is], Dict) ->
bs_restores([], Dict) -> Dict.
%% Pass 2.
-bs_replace([{test,bs_start_match2,F,Live,[Src,Ctx],CtxR}|T], Dict, Acc) when is_atom(Ctx) ->
+bs_replace([{test,bs_start_match2,F,Live,[Src,{context,Ctx}],CtxR}|T], Dict, Acc) ->
Slots = case gb_trees:lookup(Ctx, Dict) of
{value,Slots0} -> Slots0;
none -> 0
diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl
index f5afa75b16..caff47dbcb 100644
--- a/lib/compiler/src/cerl_inline.erl
+++ b/lib/compiler/src/cerl_inline.erl
@@ -1822,6 +1822,14 @@ new_var(Env) ->
Name = env__new_vname(Env),
c_var(Name).
+%% The way a template variable is used makes it necessary
+%% to make sure that it is unique in the entire function.
+%% Therefore, template variables are atoms with the prefix "@i".
+
+new_template_var(Env) ->
+ Name = env__new_tname(Env),
+ c_var(Name).
+
residualize_var(R, S) ->
S1 = count_size(weight(var), S),
{ref_to_var(R), st__set_var_referenced(R#ref.loc, S1)}.
@@ -2183,7 +2191,7 @@ make_template(E, Vs0, Env0) ->
T = make_data_skel(data_type(E), Ts),
E1 = update_data(E, data_type(E),
[hd(get_ann(T)) || T <- Ts]),
- V = new_var(Env1),
+ V = new_template_var(Env1),
Env2 = env__bind(var_name(V), E1, Env1),
{set_ann(T, [V]), [V | Vs1], Env2};
false ->
@@ -2198,7 +2206,7 @@ make_template(E, Vs0, Env0) ->
Env2 = env__bind(V, E1, Env1),
{T, Vs1, Env2};
_ ->
- V = new_var(Env0),
+ V = new_template_var(Env0),
Env1 = env__bind(var_name(V), E, Env0),
{set_ann(V, [V]), [V | Vs0], Env1}
end
@@ -2564,6 +2572,11 @@ env__is_defined(Key, Env) ->
env__new_vname(Env) ->
rec_env:new_key(Env).
+env__new_tname(Env) ->
+ rec_env:new_key(fun(I) ->
+ list_to_atom("@i"++integer_to_list(I))
+ end, Env).
+
env__new_fname(A, N, Env) ->
rec_env:new_key(fun (X) ->
S = integer_to_list(X),
diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl
index f30a0b33ac..c7a129b42c 100644
--- a/lib/compiler/src/cerl_trees.erl
+++ b/lib/compiler/src/cerl_trees.erl
@@ -22,7 +22,8 @@
-module(cerl_trees).
-export([depth/1, fold/3, free_variables/1, get_label/1, label/1, label/2,
- map/2, mapfold/3, mapfold/4, size/1, variables/1]).
+ map/2, mapfold/3, mapfold/4, next_free_variable_name/1,
+ size/1, variables/1]).
-import(cerl, [alias_pat/1, alias_var/1, ann_c_alias/3, ann_c_apply/3,
ann_c_binary/2, ann_c_bitstr/6, ann_c_call/4,
@@ -507,6 +508,7 @@ mapfold_pairs(_, _, S, []) ->
%% well-formed Core Erlang syntax tree.
%%
%% @see free_variables/1
+%% @see next_free_variable_name/1
-spec variables(cerl:cerl()) -> [cerl:var_name()].
@@ -519,6 +521,7 @@ variables(T) ->
%% @doc Like <code>variables/1</code>, but only includes variables
%% that are free in the tree.
%%
+%% @see next_free_variable_name/1
%% @see variables/1
-spec free_variables(cerl:cerl()) -> [cerl:var_name()].
@@ -678,6 +681,110 @@ var_list_names([V | Vs], A) ->
var_list_names([], A) ->
A.
+%% ---------------------------------------------------------------------
+
+%% @spec next_free_variable_name(Tree::cerl()) -> var_name()
+%%
+%% var_name() = integer()
+%%
+%% @doc Returns a integer variable name higher than any other integer
+%% variable name in the syntax tree. An exception is thrown if
+%% <code>Tree</code> does not represent a well-formed Core Erlang
+%% syntax tree.
+%%
+%% @see variables/1
+%% @see free_variables/1
+
+-spec next_free_variable_name(cerl:cerl()) -> integer().
+
+next_free_variable_name(T) ->
+ 1 + next_free(T, -1).
+
+next_free(T, Max) ->
+ case type(T) of
+ literal ->
+ Max;
+ var ->
+ case var_name(T) of
+ Int when is_integer(Int) ->
+ max(Int, Max);
+ _ ->
+ Max
+ end;
+ values ->
+ next_free_in_list(values_es(T), Max);
+ cons ->
+ next_free(cons_hd(T), next_free(cons_tl(T), Max));
+ tuple ->
+ next_free_in_list(tuple_es(T), Max);
+ map ->
+ next_free_in_list([map_arg(T)|map_es(T)], Max);
+ map_pair ->
+ next_free_in_list([map_pair_op(T),map_pair_key(T),
+ map_pair_val(T)], Max);
+ 'let' ->
+ Max1 = next_free(let_body(T), Max),
+ Max2 = next_free_in_list(let_vars(T), Max1),
+ next_free(let_arg(T), Max2);
+ seq ->
+ next_free(seq_arg(T),
+ next_free(seq_body(T), Max));
+ apply ->
+ next_free(apply_op(T),
+ next_free_in_list(apply_args(T), Max));
+ call ->
+ next_free(call_module(T),
+ next_free(call_name(T),
+ next_free_in_list(
+ call_args(T), Max)));
+ primop ->
+ next_free_in_list(primop_args(T), Max);
+ 'case' ->
+ next_free(case_arg(T),
+ next_free_in_list(case_clauses(T), Max));
+ clause ->
+ Max1 = next_free(clause_guard(T),
+ next_free(clause_body(T), Max)),
+ next_free_in_list(clause_pats(T), Max1);
+ alias ->
+ next_free(alias_var(T),
+ next_free(alias_pat(T), Max));
+ 'fun' ->
+ next_free(fun_body(T),
+ next_free_in_list(fun_vars(T), Max));
+ 'receive' ->
+ Max1 = next_free_in_list(receive_clauses(T),
+ next_free(receive_timeout(T), Max)),
+ next_free(receive_action(T), Max1);
+ 'try' ->
+ Max1 = next_free(try_body(T), Max),
+ Max2 = next_free_in_list(try_vars(T), Max1),
+ Max3 = next_free(try_handler(T), Max2),
+ Max4 = next_free_in_list(try_evars(T), Max3),
+ next_free(try_arg(T), Max4);
+ 'catch' ->
+ next_free(catch_body(T), Max);
+ binary ->
+ next_free_in_list(binary_segments(T), Max);
+ bitstr ->
+ next_free(bitstr_val(T), next_free(bitstr_size(T), Max));
+ letrec ->
+ Max1 = next_free_in_defs(letrec_defs(T), Max),
+ Max2 = next_free(letrec_body(T), Max1),
+ next_free_in_list(letrec_vars(T), Max2);
+ module ->
+ next_free_in_defs(module_defs(T), Max)
+ end.
+
+next_free_in_list([H | T], Max) ->
+ next_free_in_list(T, next_free(H, Max));
+next_free_in_list([], Max) ->
+ Max.
+
+next_free_in_defs([{_, Post} | Ds], Max) ->
+ next_free_in_defs(Ds, next_free(Post, Max));
+next_free_in_defs([], Max) ->
+ Max.
%% ---------------------------------------------------------------------
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index a9bd363ee1..395b6bd677 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -108,17 +108,29 @@
module(#c_module{defs=Ds0}=Mod, Opts) ->
put(no_inline_list_funcs, not member(inline_list_funcs, Opts)),
- case get(new_var_num) of
- undefined -> put(new_var_num, 0);
- _ -> ok
- end,
init_warnings(),
Ds1 = [function_1(D) || D <- Ds0],
+ erase(new_var_num),
erase(no_inline_list_funcs),
{ok,Mod#c_module{defs=Ds1},get_warnings()}.
function_1({#c_var{name={F,Arity}}=Name,B0}) ->
+ %% Find a suitable starting value for the variable counter. Note
+ %% that this pass assumes that new_var_name/1 returns a variable
+ %% name distinct from any variable used in the entire body of
+ %% the function. We use integers as variable names to avoid
+ %% filling up the atom table when compiling huge functions.
+ Count = cerl_trees:next_free_variable_name(B0),
+ put(new_var_num, Count),
try
+ %% Find a suitable starting value for the variable
+ %% counter. Note that this pass assumes that new_var_name/1
+ %% returns a variable name distinct from any variable used in
+ %% the entire body of the function. We use integers as
+ %% variable names to avoid filling up the atom table when
+ %% compiling huge functions.
+ Count = cerl_trees:next_free_variable_name(B0),
+ put(new_var_num, Count),
B = find_fixpoint(fun(Core) ->
%% This must be a fun!
expr(Core, value, sub_new())
@@ -2154,7 +2166,7 @@ make_var(A) ->
make_var_name() ->
N = get(new_var_num),
put(new_var_num, N+1),
- list_to_atom("@f"++integer_to_list(N)).
+ N.
letify(Bs, Body) ->
Ann = cerl:get_ann(Body),
diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl
index a8f4926e55..8808c0a3b7 100644
--- a/lib/compiler/src/v3_codegen.erl
+++ b/lib/compiler/src/v3_codegen.erl
@@ -1162,7 +1162,7 @@ select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=V}},body=B,
{Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0#cg{ctx=V}),
CtxReg = fetch_var(V, Int0),
Live = max_reg(Bef#sr.reg),
- Bis1 = [{test,bs_start_match2,{f,Tf},Live,[CtxReg,V],CtxReg},
+ Bis1 = [{test,bs_start_match2,{f,Tf},Live,[CtxReg,{context,V}],CtxReg},
{bs_save2,CtxReg,{V,V}}|Bis0],
Bis = finish_select_binary(Bis1),
{Bis,Aft,St1#cg{ctx=OldCtx}};
@@ -1174,7 +1174,8 @@ select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ivar}},body=B,
{Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0#cg{ctx=Ivar}),
CtxReg = fetch_var(Ivar, Int0),
Live = max_reg(Bef#sr.reg),
- Bis1 = [{test,bs_start_match2,{f,Tf},Live,[fetch_var(V, Bef),Ivar],CtxReg},
+ Bis1 = [{test,bs_start_match2,{f,Tf},Live,
+ [fetch_var(V, Bef),{context,Ivar}],CtxReg},
{bs_save2,CtxReg,{Ivar,Ivar}}|Bis0],
Bis = finish_select_binary(Bis1),
{Bis,Aft,St1#cg{ctx=OldCtx}}.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 6029b91cdc..4799105d05 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -1152,7 +1152,7 @@ fun_tq(Cs0, L, St0, NameInfo) ->
%% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
%% This TQ from Simon PJ pp 127-138.
-lc_tq(Line, E, [#igen{anno=GAnno,ceps=Ceps,
+lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
arg={Pre,Arg}}|Qs], Mc, St0) ->
@@ -1162,7 +1162,7 @@ lc_tq(Line, E, [#igen{anno=GAnno,ceps=Ceps,
F = #c_var{anno=LA,name={Name,1}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail]},
{Var,St2} = new_var(St1),
- Fc = function_clause([Var], LA, {Name,1}),
+ Fc = function_clause([Var], GA, {Name,1}),
TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]},
Cs0 = case {AccPat,AccGuard} of
{SkipPat,[]} ->
@@ -1185,9 +1185,9 @@ lc_tq(Line, E, [#igen{anno=GAnno,ceps=Ceps,
body=Lps ++ [Lc]}|Cs0],
St3}
end,
- Fun = #ifun{anno=LAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
- {#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,1},Fun}],
- body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg]}]},
+ Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
+ {#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
+ body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
Ceps,St4};
lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
@@ -2005,7 +2005,7 @@ new_fun_name(Type, #core{fcount=C}=St) ->
%% new_var_name(State) -> {VarName,State}.
new_var_name(#core{vcount=C}=St) ->
- {list_to_atom("@c" ++ integer_to_list(C)),St#core{vcount=C + 1}}.
+ {C,St#core{vcount=C + 1}}.
%% new_var(State) -> {{var,Name},State}.
%% new_var(LineAnno, State) -> {{var,Name},State}.
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index dfe8d26afb..4e3ceedbc0 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -157,7 +157,13 @@ include_attribute(_) -> true.
function({#c_var{name={F,Arity}=FA},Body}, St0) ->
%%io:format("~w/~w~n", [F,Arity]),
try
- St1 = St0#kern{func=FA,ff=undefined,vcount=0,fcount=0,ds=cerl_sets:new()},
+ %% Find a suitable starting value for the variable counter. Note
+ %% that this pass assumes that new_var_name/1 returns a variable
+ %% name distinct from any variable used in the entire body of
+ %% the function. We use integers as variable names to avoid
+ %% filling up the atom table when compiling huge functions.
+ Count = cerl_trees:next_free_variable_name(Body),
+ St1 = St0#kern{func=FA,ff=undefined,vcount=Count,fcount=0,ds=cerl_sets:new()},
{#ifun{anno=Ab,vars=Kvs,body=B0},[],St2} = expr(Body, new_sub(), St1),
{B1,_,St3} = ubody(B0, return, St2),
%%B1 = B0, St3 = St2, %Null second pass
@@ -168,7 +174,6 @@ function({#c_var{name={F,Arity}=FA},Body}, St0) ->
erlang:raise(Class, Error, Stack)
end.
-
%% body(Cexpr, Sub, State) -> {Kexpr,[PreKepxr],State}.
%% Do the main sequence of a body. A body ends in an atomic value or
%% values. Must check if vector first so do expr.
@@ -1356,7 +1361,7 @@ new_fun_name(Type, #kern{func={F,Arity},fcount=C}=St) ->
%% new_var_name(State) -> {VarName,State}.
new_var_name(#kern{vcount=C}=St) ->
- {list_to_atom("@k" ++ integer_to_list(C)),St#kern{vcount=C+1}}.
+ {C,St#kern{vcount=C+1}}.
%% new_var(State) -> {#k_var{},State}.
diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl
index 9ad417b09b..699081470d 100644
--- a/lib/compiler/test/lc_SUITE.erl
+++ b/lib/compiler/test/lc_SUITE.erl
@@ -107,6 +107,31 @@ basic(Config) when is_list(Config) ->
[] = [X || X <- L1, X+1 < 2],
{'EXIT',_} = (catch [X || X <- L1, odd(X)]),
fc([x], catch [E || E <- id(x)]),
+
+ %% Make sure that line numbers point out the generator.
+ case ?MODULE of
+ lc_inline_SUITE ->
+ ok;
+ _ ->
+ {'EXIT',{function_clause,
+ [{?MODULE,_,_,
+ [{file,"bad_lc.erl"},{line,4}]}|_]}} =
+ (catch bad_generator(a)),
+ {'EXIT',{function_clause,
+ [{?MODULE,_,_,
+ [{file,"bad_lc.erl"},{line,4}]}|_]}} =
+ (catch bad_generator([a|b])),
+ {'EXIT',{badarg,
+ [{erlang,length,_,_},
+ {?MODULE,bad_generator_bc,1,
+ [{file,"bad_lc.erl"},{line,7}]}|_]}} =
+ (catch bad_generator_bc(a)),
+ {'EXIT',{badarg,
+ [{erlang,length,_,_},
+ {?MODULE,bad_generator_bc,1,
+ [{file,"bad_lc.erl"},{line,7}]}|_]}} =
+ (catch bad_generator_bc([a|b]))
+ end,
ok.
tuple_list() ->
@@ -249,3 +274,11 @@ fc(Args, {'EXIT',{function_clause,[{?MODULE,_,Arity,_}|_]}})
fc(Args, {'EXIT',{{case_clause,ActualArgs},_}})
when ?MODULE =:= lc_inline_SUITE ->
Args = tuple_to_list(ActualArgs).
+
+-file("bad_lc.erl", 1).
+bad_generator(List) -> %Line 2
+ [I || %Line 3
+ I <- List]. %Line 4
+bad_generator_bc(List) -> %Line 5
+ << <<I:4>> || %Line 6
+ I <- List>>. %Line 7
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index ec2a1dba0a..46775989ae 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -847,8 +847,13 @@ on_load() ->
case Status of
ok -> ok;
{error, {E, Str}} ->
- error_logger:error_msg("Unable to load crypto library. Failed with error:~n\"~p, ~s\"~n"
- "OpenSSL might not be installed on this system.~n",[E,Str]),
+ Fmt = "Unable to load crypto library. Failed with error:~n\"~p, ~s\"~n~s",
+ Extra = case E of
+ load_failed ->
+ "OpenSSL might not be installed on this system.\n";
+ _ -> ""
+ end,
+ error_logger:error_msg(Fmt, [E,Str,Extra]),
Status
end.
diff --git a/lib/dialyzer/test/small_SUITE_data/results/fun_arity b/lib/dialyzer/test/small_SUITE_data/results/fun_arity
index e916b2483f..8b7a538758 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/fun_arity
+++ b/lib/dialyzer/test/small_SUITE_data/results/fun_arity
@@ -1,37 +1,37 @@
-fun_arity.erl:100: Fun application will fail since _@c1 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:100: Fun application will fail since _1 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:100: Function 'Mfa_0_ko'/1 has no local return
-fun_arity.erl:104: Fun application will fail since _@c1 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:104: Fun application will fail since _1 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:104: Function 'Mfa_1_ko'/1 has no local return
-fun_arity.erl:111: Fun application will fail since _@c1 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:111: Fun application will fail since _1 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:111: Function mFa_0_ko/1 has no local return
-fun_arity.erl:115: Fun application will fail since _@c1 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:115: Fun application will fail since _1 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:115: Function mFa_1_ko/1 has no local return
-fun_arity.erl:122: Fun application will fail since _@c2 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:122: Fun application will fail since _2 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:122: Function 'MFa_0_ko'/2 has no local return
-fun_arity.erl:126: Fun application will fail since _@c2 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:126: Fun application will fail since _2 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:126: Function 'MFa_1_ko'/2 has no local return
-fun_arity.erl:35: Fun application will fail since _@c0 :: fun(() -> 'ok') is not a function of arity 1
+fun_arity.erl:35: Fun application will fail since _0 :: fun(() -> 'ok') is not a function of arity 1
fun_arity.erl:35: Function f_0_ko/0 has no local return
-fun_arity.erl:39: Fun application will fail since _@c0 :: fun((_) -> 'ok') is not a function of arity 0
+fun_arity.erl:39: Fun application will fail since _0 :: fun((_) -> 'ok') is not a function of arity 0
fun_arity.erl:39: Function f_1_ko/0 has no local return
-fun_arity.erl:48: Fun application will fail since _@c0 :: fun(() -> 'ok') is not a function of arity 1
+fun_arity.erl:48: Fun application will fail since _0 :: fun(() -> 'ok') is not a function of arity 1
fun_arity.erl:48: Function fa_0_ko/0 has no local return
-fun_arity.erl:53: Fun application will fail since _@c0 :: fun((_) -> 'ok') is not a function of arity 0
+fun_arity.erl:53: Fun application will fail since _0 :: fun((_) -> 'ok') is not a function of arity 0
fun_arity.erl:53: Function fa_1_ko/0 has no local return
-fun_arity.erl:63: Fun application will fail since _@c0 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:63: Fun application will fail since _0 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:63: Function mfa_0_ko/0 has no local return
-fun_arity.erl:68: Fun application will fail since _@c0 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:68: Fun application will fail since _0 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:68: Function mfa_1_ko/0 has no local return
-fun_arity.erl:76: Fun application will fail since _@c0 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:76: Fun application will fail since _0 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:76: Function mfa_ne_0_ko/0 has no local return
fun_arity.erl:78: Function mf_ne/0 will never be called
-fun_arity.erl:81: Fun application will fail since _@c0 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:81: Fun application will fail since _0 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:81: Function mfa_ne_1_ko/0 has no local return
fun_arity.erl:83: Function mf_ne/1 will never be called
-fun_arity.erl:89: Fun application will fail since _@c0 :: fun(() -> any()) is not a function of arity 1
+fun_arity.erl:89: Fun application will fail since _0 :: fun(() -> any()) is not a function of arity 1
fun_arity.erl:89: Function mfa_nd_0_ko/0 has no local return
fun_arity.erl:90: Call to missing or unexported function fun_arity:mf_nd/0
-fun_arity.erl:93: Fun application will fail since _@c0 :: fun((_) -> any()) is not a function of arity 0
+fun_arity.erl:93: Fun application will fail since _0 :: fun((_) -> any()) is not a function of arity 0
fun_arity.erl:93: Function mfa_nd_1_ko/0 has no local return
fun_arity.erl:94: Call to missing or unexported function fun_arity:mf_nd/1
diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml
index 197851021f..69eb12a8a0 100644
--- a/lib/kernel/doc/src/seq_trace.xml
+++ b/lib/kernel/doc/src/seq_trace.xml
@@ -80,13 +80,18 @@ seq_trace:set_token(OldToken), % activate the trace token again
<p>Sets the individual <c><anno>Component</anno></c> of the trace token to
<c><anno>Val</anno></c>. Returns the previous value of the component.</p>
<taglist>
- <tag><c>set_token(label, <anno>Integer</anno>)</c></tag>
+ <tag><c>set_token(label, <anno>Label</anno>)</c></tag>
<item>
- <p>The <c>label</c> component is an integer which
+ <p>The <c>label</c> component is a term which
identifies all events belonging to the same sequential
trace. If several sequential traces can be active
simultaneously, <c>label</c> is used to identify
the separate traces. Default is 0.</p>
+ <warning>
+ <p>Labels were restricted to small signed integers (28 bits)
+ prior to OTP 21. The trace token will be silenty dropped if it
+ crosses over to a node that does not support the label.</p>
+ </warning>
</item>
<tag><c>set_token(serial, SerialValue)</c></tag>
<item>
diff --git a/lib/kernel/include/dist.hrl b/lib/kernel/include/dist.hrl
index b7c35712a6..6baaa35d72 100644
--- a/lib/kernel/include/dist.hrl
+++ b/lib/kernel/include/dist.hrl
@@ -41,6 +41,7 @@
-define(DFLAG_MAP_TAG, 16#20000).
-define(DFLAG_BIG_CREATION, 16#40000).
-define(DFLAG_SEND_SENDER, 16#80000).
+-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index f7a84c14b4..781397e1ee 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -113,6 +113,8 @@ dflag2str(?DFLAG_BIG_CREATION) ->
"BIG_CREATION";
dflag2str(?DFLAG_SEND_SENDER) ->
"SEND_SENDER";
+dflag2str(?DFLAG_BIG_SEQTRACE_LABELS) ->
+ "BIG_SEQTRACE_LABELS";
dflag2str(_) ->
"UNKNOWN".
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index c2df1ee288..57d8fc7a15 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -101,14 +101,25 @@
-type deep_list() :: [char() | atom() | deep_list()].
-type name() :: string() | atom() | deep_list().
-type name_all() :: string() | atom() | deep_list() | (RawFilename :: binary()).
--type posix() :: 'eacces' | 'eagain' | 'ebadf' | 'ebusy' | 'edquot'
- | 'eexist' | 'efault' | 'efbig' | 'eintr' | 'einval'
- | 'eio' | 'eisdir' | 'eloop' | 'emfile' | 'emlink'
- | 'enametoolong'
- | 'enfile' | 'enodev' | 'enoent' | 'enomem' | 'enospc'
- | 'enotblk' | 'enotdir' | 'enotsup' | 'enxio' | 'eperm'
- | 'epipe' | 'erofs' | 'espipe' | 'esrch' | 'estale'
- | 'exdev'.
+-type posix() ::
+ 'eacces' | 'eagain' |
+ 'ebadf' | 'ebadmsg' | 'ebusy' |
+ 'edeadlk' | 'edeadlock' | 'edquot' |
+ 'eexist' |
+ 'efault' | 'efbig' | 'eftype' |
+ 'eintr' | 'einval' | 'eio' | 'eisdir' |
+ 'eloop' |
+ 'emfile' | 'emlink' | 'emultihop' |
+ 'enametoolong' | 'enfile' |
+ 'enobufs' | 'enodev' | 'enolck' | 'enolink' | 'enoent' |
+ 'enomem' | 'enospc' | 'enosr' | 'enostr' | 'enosys' |
+ 'enotblk' | 'enotdir' | 'enotsup' | 'enxio' |
+ 'eopnotsupp' | 'eoverflow' |
+ 'eperm' | 'epipe' |
+ 'erange' | 'erofs' |
+ 'espipe' | 'esrch' | 'estale' |
+ 'etxtbsy' |
+ 'exdev'.
-type date_time() :: calendar:datetime().
-type posix_file_advise() :: 'normal' | 'sequential' | 'random'
| 'no_reuse' | 'will_need' | 'dont_need'.
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index 4bad523dff..73c53b9011 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -105,7 +105,20 @@
{local, binary()} |
{unspec, <<>>} |
{undefined, any()}.
--type posix() :: exbadport | exbadseq | file:posix().
+-type posix() ::
+ 'eaddrinuse' | 'eaddrnotavail' | 'eafnosupport' | 'ealready' |
+ 'econnaborted' | 'econnrefused' | 'econnreset' |
+ 'edestaddrreq' |
+ 'ehostdown' | 'ehostunreach' |
+ 'einprogress' | 'eisconn' |
+ 'emsgsize' |
+ 'enetdown' | 'enetunreach' |
+ 'enopkg' | 'enoprotoopt' | 'enotconn' | 'enotty' | 'enotsock' |
+ 'eproto' | 'eprotonosupport' | 'eprototype' |
+ 'esocktnosupport' |
+ 'etimedout' |
+ 'ewouldblock' |
+ 'exbadport' | 'exbadseq' | file:posix().
-type socket() :: port().
-type socket_setopt() ::
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index cc0c10909b..8d7aba0f27 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -41,7 +41,7 @@
-type flag() :: 'send' | 'receive' | 'print' | 'timestamp' | 'monotonic_timestamp' | 'strict_monotonic_timestamp'.
-type component() :: 'label' | 'serial' | flag().
--type value() :: (Integer :: non_neg_integer())
+-type value() :: (Label :: term())
| {Previous :: non_neg_integer(),
Current :: non_neg_integer()}
| (Bool :: boolean()).
@@ -59,10 +59,6 @@ set_token({Flags,Label,Serial,_From,Lastcnt}) ->
F = decode_flags(Flags),
set_token2([{label,Label},{serial,{Lastcnt, Serial}} | F]).
-%% We limit the label type to always be a small integer because erl_interface
-%% expects that, the BIF can however "unofficially" handle atoms as well, and
-%% atoms can be used if only Erlang nodes are involved
-
-spec set_token(Component, Val) -> {Component, OldVal} when
Component :: component(),
Val :: value(),
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index eea9e43dd3..ff93f25e25 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -2212,7 +2212,7 @@ e_delete(Config) when is_list(Config) ->
Base, #file_info {mode=0}),
{error, eacces} = ?FILE_MODULE:delete(Afile),
?FILE_MODULE:write_file_info(
- Base, #file_info {mode=8#600})
+ Base, #file_info {mode=8#700})
end,
[] = flush(),
@@ -2343,7 +2343,7 @@ e_make_dir(Config) when is_list(Config) ->
?FILE_MODULE:write_file_info(Base, #file_info {mode=0}),
{error, eacces} = ?FILE_MODULE:make_dir(filename:join(Base, "xxxx")),
?FILE_MODULE:write_file_info(
- Base, #file_info {mode=8#600})
+ Base, #file_info {mode=8#700})
end,
ok.
@@ -2389,7 +2389,7 @@ e_del_dir(Config) when is_list(Config) ->
ok = ?FILE_MODULE:make_dir(ADirectory),
?FILE_MODULE:write_file_info( Base, #file_info {mode=0}),
{error, eacces} = ?FILE_MODULE:del_dir(ADirectory),
- ?FILE_MODULE:write_file_info( Base, #file_info {mode=8#600})
+ ?FILE_MODULE:write_file_info( Base, #file_info {mode=8#700})
end,
[] = flush(),
ok.
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index ab62f4dc34..5bb230d1c4 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -1306,7 +1306,7 @@ e_delete(Config) when is_list(Config) ->
Base, #file_info {mode=0}),
{error, eacces} = ?PRIM_FILE:delete(Afile),
?PRIM_FILE:write_file_info(
- Base, #file_info {mode=8#600})
+ Base, #file_info {mode=8#700})
end,
ok.
@@ -1442,7 +1442,7 @@ e_make_dir(Config) when is_list(Config) ->
?PRIM_FILE:write_file_info(Base, #file_info {mode=0}),
{error, eacces} =
?PRIM_FILE:make_dir(filename:join(Base, "xxxx")),
- ?PRIM_FILE:write_file_info(Base, #file_info {mode=8#600})
+ ?PRIM_FILE:write_file_info(Base, #file_info {mode=8#700})
end,
ok.
@@ -1492,7 +1492,7 @@ e_del_dir(Config) when is_list(Config) ->
?PRIM_FILE:write_file_info(Base, #file_info {mode=0}),
{error, eacces} = ?PRIM_FILE:del_dir(ADirectory),
?PRIM_FILE:write_file_info(
- Base, #file_info {mode=8#600})
+ Base, #file_info {mode=8#700})
end,
ok.
diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl
index be23a1933f..aae8a83304 100644
--- a/lib/kernel/test/seq_trace_SUITE.erl
+++ b/lib/kernel/test/seq_trace_SUITE.erl
@@ -25,7 +25,7 @@
-export([token_set_get/1, tracer_set_get/1, print/1,
send/1, distributed_send/1, recv/1, distributed_recv/1,
trace_exit/1, distributed_exit/1, call/1, port/1,
- match_set_seq_token/1, gc_seq_token/1]).
+ match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1]).
%% internal exports
-export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1,
@@ -47,7 +47,7 @@ all() ->
[token_set_get, tracer_set_get, print, send,
distributed_send, recv, distributed_recv, trace_exit,
distributed_exit, call, port, match_set_seq_token,
- gc_seq_token].
+ gc_seq_token, label_capability_mismatch].
groups() ->
[].
@@ -90,8 +90,8 @@ do_token_set_get(TsType) ->
%% Test that initial seq_trace is disabled
[] = seq_trace:get_token(),
%% Test setting and reading the different fields
- 0 = seq_trace:set_token(label,17),
- {label,17} = seq_trace:get_token(label),
+ 0 = seq_trace:set_token(label,{my_label,1}),
+ {label,{my_label,1}} = seq_trace:get_token(label),
false = seq_trace:set_token(print,true),
{print,true} = seq_trace:get_token(print),
false = seq_trace:set_token(send,true),
@@ -101,12 +101,12 @@ do_token_set_get(TsType) ->
false = seq_trace:set_token(TsType,true),
{TsType,true} = seq_trace:get_token(TsType),
%% Check the whole token
- {Flags,17,0,Self,0} = seq_trace:get_token(), % all flags are set
+ {Flags,{my_label,1},0,Self,0} = seq_trace:get_token(), % all flags are set
%% Test setting and reading the 'serial' field
{0,0} = seq_trace:set_token(serial,{3,5}),
{serial,{3,5}} = seq_trace:get_token(serial),
%% Check the whole token, test that a whole token can be set and get
- {Flags,17,5,Self,3} = seq_trace:get_token(),
+ {Flags,{my_label,1},5,Self,3} = seq_trace:get_token(),
seq_trace:set_token({Flags,19,7,Self,5}),
{Flags,19,7,Self,5} = seq_trace:get_token(),
%% Check that receive timeout does not reset token
@@ -166,11 +166,13 @@ do_send(TsType) ->
seq_trace:reset_trace(),
start_tracer(),
Receiver = spawn(?MODULE,one_time_receiver,[]),
+ Label = make_ref(),
+ seq_trace:set_token(label,Label),
set_token_flags([send, TsType]),
Receiver ! send,
Self = self(),
seq_trace:reset_trace(),
- [{0,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1),
+ [{Label,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1),
check_ts(TsType, Ts).
distributed_send(Config) when is_list(Config) ->
@@ -184,14 +186,19 @@ do_distributed_send(TsType) ->
seq_trace:reset_trace(),
start_tracer(),
Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
+
+ %% Make sure complex labels survive the trip.
+ Label = make_ref(),
+ seq_trace:set_token(label,Label),
set_token_flags([send,TsType]),
+
Receiver ! send,
Self = self(),
seq_trace:reset_trace(),
stop_node(Node),
- [{0,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1),
+ [{Label,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1),
check_ts(TsType, Ts).
-
+
recv(Config) when is_list(Config) ->
lists:foreach(fun do_recv/1, ?TIMESTAMP_MODES).
@@ -220,7 +227,12 @@ do_distributed_recv(TsType) ->
seq_trace:reset_trace(),
rpc:call(Node,?MODULE,start_tracer,[]),
Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
+
+ %% Make sure complex labels survive the trip.
+ Label = make_ref(),
+ seq_trace:set_token(label,Label),
set_token_flags(['receive',TsType]),
+
Receiver ! 'receive',
%% let the other process receive the message:
receive after 1 -> ok end,
@@ -229,7 +241,7 @@ do_distributed_recv(TsType) ->
Result = rpc:call(Node,?MODULE,stop_tracer,[1]),
stop_node(Node),
ok = io:format("~p~n",[Result]),
- [{0,{'receive',_,Self,Receiver,'receive'}, Ts}] = Result,
+ [{Label,{'receive',_,Self,Receiver,'receive'}, Ts}] = Result,
check_ts(TsType, Ts).
trace_exit(Config) when is_list(Config) ->
@@ -240,7 +252,12 @@ do_trace_exit(TsType) ->
start_tracer(),
Receiver = spawn_link(?MODULE, one_time_receiver, [exit]),
process_flag(trap_exit, true),
+
+ %% Make sure complex labels survive the trip.
+ Label = make_ref(),
+ seq_trace:set_token(label,Label),
set_token_flags([send, TsType]),
+
Receiver ! {before, exit},
%% let the other process receive the message:
receive
@@ -254,8 +271,8 @@ do_trace_exit(TsType) ->
Result = stop_tracer(2),
seq_trace:reset_trace(),
ok = io:format("~p~n", [Result]),
- [{0, {send, {0,1}, Self, Receiver, {before, exit}}, Ts0},
- {0, {send, {1,2}, Receiver, Self,
+ [{Label, {send, {0,1}, Self, Receiver, {before, exit}}, Ts0},
+ {Label, {send, {1,2}, Receiver, Self,
{'EXIT', Receiver, {exit, {before, exit}}}}, Ts1}] = Result,
check_ts(TsType, Ts0),
check_ts(TsType, Ts1).
@@ -291,6 +308,74 @@ do_distributed_exit(TsType) ->
{'EXIT', Receiver, {exit, {before, exit}}}}, Ts}] = Result,
check_ts(TsType, Ts).
+label_capability_mismatch(Config) when is_list(Config) ->
+ Releases = ["20_latest"],
+ Available = [Rel || Rel <- Releases, test_server:is_release_available(Rel)],
+ case Available of
+ [] -> {skipped, "No incompatible releases available"};
+ _ ->
+ lists:foreach(fun do_incompatible_labels/1, Available),
+ lists:foreach(fun do_compatible_labels/1, Available),
+ ok
+ end.
+
+do_incompatible_labels(Rel) ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ {ok, Node} = test_server:start_node(
+ list_to_atom(atom_to_list(?MODULE)++"_"++Rel), peer,
+ [{args, " -setcookie "++Cookie}, {erl, [{release, Rel}]}]),
+
+ {_,Dir} = code:is_loaded(?MODULE),
+ Mdir = filename:dirname(Dir),
+ true = rpc:call(Node,code,add_patha,[Mdir]),
+ seq_trace:reset_trace(),
+ rpc:call(Node,?MODULE,start_tracer,[]),
+ Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
+
+ %% This node does not support arbitrary labels, so it must fail with a
+ %% timeout as the token is dropped silently.
+ seq_trace:set_token(label,make_ref()),
+ seq_trace:set_token('receive',true),
+
+ Receiver ! 'receive',
+ %% let the other process receive the message:
+ receive after 10 -> ok end,
+ seq_trace:reset_trace(),
+
+ {error,timeout} = rpc:call(Node,?MODULE,stop_tracer,[1]),
+ stop_node(Node),
+ ok.
+
+do_compatible_labels(Rel) ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ {ok, Node} = test_server:start_node(
+ list_to_atom(atom_to_list(?MODULE)++"_"++Rel), peer,
+ [{args, " -setcookie "++Cookie}, {erl, [{release, Rel}]}]),
+
+ {_,Dir} = code:is_loaded(?MODULE),
+ Mdir = filename:dirname(Dir),
+ true = rpc:call(Node,code,add_patha,[Mdir]),
+ seq_trace:reset_trace(),
+ rpc:call(Node,?MODULE,start_tracer,[]),
+ Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
+
+ %% This node does not support arbitrary labels, but small integers should
+ %% still work.
+ Label = 1234,
+ seq_trace:set_token(label,Label),
+ seq_trace:set_token('receive',true),
+
+ Receiver ! 'receive',
+ %% let the other process receive the message:
+ receive after 10 -> ok end,
+ Self = self(),
+ seq_trace:reset_trace(),
+ Result = rpc:call(Node,?MODULE,stop_tracer,[1]),
+ stop_node(Node),
+ ok = io:format("~p~n",[Result]),
+ [{Label,{'receive',_,Self,Receiver,'receive'}, _}] = Result,
+ ok.
+
call(doc) ->
"Tests special forms {is_seq_trace} and {get_seq_token} "
"in trace match specs.";
diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml
index 95f74d4607..276a41c415 100644
--- a/lib/runtime_tools/doc/src/dbg.xml
+++ b/lib/runtime_tools/doc/src/dbg.xml
@@ -815,7 +815,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<v>HandlerSpec = {HandlerFun, InitialData}</v>
<v>HandlerFun = fun() (two arguments)</v>
<v>ModuleSpec = fun() (no arguments) | {TracerModule, TracerState}</v>
- <v>ModuleModule = atom()</v>
+ <v>TracerModule = atom()</v>
<v>InitialData = TracerState = term()</v>
</type>
<desc>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index acf94ff6af..d36be8431c 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -67,24 +67,41 @@
<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() -
as returned by <c>ssh:daemon/[1,2,3]</c></p></item>
+
+ <tag><c>ok_error(OKtype) = </c></tag>
+ <item><p><c>{ok,OKtype} | {error, term()}</c></p></item>
+
+ <tag><c>ok_error() = </c></tag>
+ <item><p><c>ok_error(term())</c></p></item>
+
<tag><c>ssh_connection_ref() =</c></tag>
<item><p>opaque() - as returned by <c>ssh:connect/3</c></p></item>
+
<tag><c>ip_address() =</c></tag>
- <item><p><c>inet::ip_address</c></p></item>
+ <item><p><c>inet::ip_address()</c></p></item>
+
+ <tag><c>port_number() =</c></tag>
+ <item><p><c>inet::port_number()</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>key_cb() =</c></tag>
<item>
<p><c>atom() | {atom(), list()}</c></p>
@@ -94,6 +111,7 @@
case maybe.</p>
<p><c>list()</c> - List of options that can be passed to the callback module.</p>
</item>
+
<tag><c>channel_init_args() =</c></tag>
<item><p><c>list()</c></p></item>
@@ -478,8 +496,8 @@
<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>
+ User::string() | Peer::{inet:hostname(), {ip_address(), port_number()}} |
+ Sockname::{ip_address(), port_number()}</v>
</type>
<desc>
<p>Retrieves information about a connection.</p>
@@ -541,22 +559,83 @@
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} |
+
+ <tag><marker id="daemon_opt_shell"/>
+ <c><![CDATA[{shell, {Module, Function, Args} |
fun(string() = User) - > pid() | fun(string() = User,
ip_address() = PeerAddr) -> pid()}]]></c></tag>
<item>
<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>
+ <p>See the option <seealso marker="#daemon_opt_exec"><c>exec</c></seealso>
+ for a description of how the daemon execute exec-requests depending on
+ the shell- and exec-options.</p>
+ </item>
+
+ <tag><marker id="daemon_opt_exec"/>
+ <c><![CDATA[{exec, {direct, exec_spec()}}]]></c>
+ <br/><c>where:</c>
+ <br/><c>exec_spec() = </c>
+ <br/><c> fun(Cmd::string()) -> ok_error()</c>
+ <br/><c> | fun(Cmd::string(), User::string()) -> ok_error()</c>
+ <br/><c> | fun(Cmd::string(), User::string(), ClientAddr::{ip_address(), port_number()}) -> ok_error()</c>
+ </tag>
+ <item>
+ <p>This option changes how the daemon execute exec-requests from clients. The term in <c>ok_error()</c>
+ is formatted to a string if it is a non-string type. No trailing newline is added in the ok-case but in the
+ error case.</p>
+ <p>Error texts are returned on channel-type 1 which usually are piped to <c>stderr</c> on e.g Linux systems.
+ Texts from a successful execution will in similar manner be piped to <c>stdout</c>. The exit-status code
+ is set to 0 for success and -1 for errors. The exact results presented on the client side depends on the
+ client.
+ </p>
+ <p>The option cooperates with the daemon-option <seealso marker="#daemon_opt_shell"><c>shell</c></seealso>
+ in the following way:</p>
+ <taglist>
+ <tag>1. If the exec-option is present (the shell-option may or may not be present):</tag>
+ <item>
+ <p>The exec-option fun is called with the same number of parameters as the arity of the fun,
+ and the result is returned to the client.
+ </p>
+ </item>
+
+ <tag>2. If the exec-option is absent, but a shell-option is present with the default Erlang shell:</tag>
+ <item>
+ <p>The default Erlang evaluator is used and the result is returned to the client.</p>
+ </item>
+
+ <tag>3. If the exec-option is absent, but a shell-option is present that is not the default Erlang shell:</tag>
+ <item>
+ <p>The exec-request is not evaluated and an error message is returned to the client.</p>
+ </item>
+
+ <tag>4. If neither the exec-option nor the shell-option is present:</tag>
+ <item>
+ <p>The default Erlang evaluator is used and the result is returned to the client.</p>
+ </item>
+ </taglist>
+ <p>If a custom CLI is installed (see the option <seealso marker="#daemon_opt_ssh_cli"><c>ssh_cli</c></seealso>)
+ the rules above are replaced by thoose implied by the custom CLI.
+ </p>
+ <note>
+ <p>The exec-option has existed for a long time but has not previously been documented. The old
+ definition and behaviour are retained but obey the rules 1-4 above if conflicting.
+ The old and undocumented style should not be used in new programs.</p>
+ </note>
</item>
- <tag><c><![CDATA[{ssh_cli, {channel_callback(),
+
+ <tag><marker id="daemon_opt_ssh_cli"/>
+ <c><![CDATA[{ssh_cli, {channel_callback(),
channel_init_args()} | no_cli}]]></c></tag>
<item>
<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
+ option <seealso marker="#daemon_opt_shell"><c>shell</c></seealso>. This means less work than implementing
+ an own CLI channel. If <c>ssh_cli</c> is set to <c>no_cli</c>, the CLI channels
+ like <seealso marker="#daemon_opt_shell"><c>shell</c></seealso>
+ and <seealso marker="#daemon_opt_exec"><c>exec</c></seealso>
are disabled and only subsystem channels are allowed.</p>
</item>
<tag><c><![CDATA[{user_dir, string()}]]></c></tag>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 150d46a9a2..72830de04d 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -428,7 +428,7 @@
</func>
<func>
- <name>shell(ConnectionRef, ChannelId) -> ssh_request_status() | {error, closed}
+ <name>shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed}
</name>
<fsummary>Requests that the user default shell (typically defined in
/etc/passwd in Unix systems) is to be executed at the server end.</fsummary>
@@ -440,6 +440,10 @@
<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>
+ <p>Note: the return value is <c>ok</c> instead of <c>success</c> unlike in other
+ functions in this module. This is a fault that was introduced so long ago that
+ any change would break a large number of existing software.
+ </p>
</desc>
</func>
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 958c342f5f..783f2f80c0 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -118,42 +118,53 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{pty = Pty, buf = NewBuf}};
-handle_ssh_msg({ssh_cm, ConnectionHandler,
- {shell, ChannelId, WantReply}}, State) ->
+handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State) ->
NewState = start_shell(ConnectionHandler, State),
- ssh_connection:reply_request(ConnectionHandler, WantReply,
- success, ChannelId),
- {ok, NewState#state{channel = ChannelId,
- cm = ConnectionHandler}};
-
-handle_ssh_msg({ssh_cm, ConnectionHandler,
- {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined,
- shell=?DEFAULT_SHELL} = State) ->
- {Reply, Status} = exec(Cmd),
- write_chars(ConnectionHandler,
- ChannelId, io_lib:format("~p\n", [Reply])),
- ssh_connection:reply_request(ConnectionHandler, WantReply,
- success, ChannelId),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, Status),
- ssh_connection:send_eof(ConnectionHandler, ChannelId),
- {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}};
-
-handle_ssh_msg({ssh_cm, ConnectionHandler,
- {exec, ChannelId, WantReply, _Cmd}}, #state{exec = undefined} = State) ->
- write_chars(ConnectionHandler, ChannelId, 1, "Prohibited.\n"),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, 255),
- ssh_connection:send_eof(ConnectionHandler, ChannelId),
- {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}};
-
-handle_ssh_msg({ssh_cm, ConnectionHandler,
- {exec, ChannelId, WantReply, Cmd}}, State) ->
- NewState = start_shell(ConnectionHandler, Cmd, State),
- ssh_connection:reply_request(ConnectionHandler, WantReply,
- success, ChannelId),
{ok, NewState#state{channel = ChannelId,
cm = ConnectionHandler}};
+handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, S0) ->
+ case
+ case S0#state.exec of
+ {direct,F} ->
+ %% Exec called and a Fun or MFA is defined to use. The F returns the
+ %% value to return.
+ exec_direct(ConnectionHandler, F, Cmd);
+
+ undefined when S0#state.shell == ?DEFAULT_SHELL ->
+ %% Exec called and the shell is the default shell (= Erlang shell).
+ %% To be exact, eval the term as an Erlang term (but not using the
+ %% ?DEFAULT_SHELL directly). This disables banner, prompts and such.
+ exec_in_erlang_default_shell(Cmd);
+
+ undefined ->
+ %% Exec called, but the a shell other than the default shell is defined.
+ %% No new exec shell is defined, so don't execute!
+ %% We don't know if it is intended to use the new shell or not.
+ {"Prohibited.", 255, 1};
+
+ _ ->
+ %% Exec called and a Fun or MFA is defined to use. The F communicates via
+ %% standard io:write/read.
+ %% Kept for compatibility.
+ S1 = start_exec_shell(ConnectionHandler, Cmd, S0),
+ ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
+ {ok, S1}
+ end
+ of
+ {Reply, Status, Type} ->
+ write_chars(ConnectionHandler, ChannelId, Type, Reply),
+ ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
+ ssh_connection:exit_status(ConnectionHandler, ChannelId, Status),
+ ssh_connection:send_eof(ConnectionHandler, ChannelId),
+ {stop, ChannelId, S0#state{channel = ChannelId, cm = ConnectionHandler}};
+
+ {ok, S} ->
+ {ok, S#state{channel = ChannelId,
+ cm = ConnectionHandler}}
+ end;
+
handle_ssh_msg({ssh_cm, _ConnectionHandler, {eof, _ChannelId}}, State) ->
{ok, State};
@@ -259,35 +270,7 @@ to_group(Data, Group) ->
end,
to_group(Tail, Group).
-exec(Cmd) ->
- case eval(parse(scan(Cmd))) of
- {error, _} ->
- {Cmd, 0}; %% This should be an external call
- Term ->
- Term
- end.
-
-scan(Cmd) ->
- erl_scan:string(Cmd).
-
-parse({ok, Tokens, _}) ->
- erl_parse:parse_exprs(Tokens);
-parse(Error) ->
- Error.
-
-eval({ok, Expr_list}) ->
- case (catch erl_eval:exprs(Expr_list,
- erl_eval:new_bindings())) of
- {value, Value, _NewBindings} ->
- {Value, 0};
- {'EXIT', {Error, _}} ->
- {Error, -1};
- Error ->
- {Error, -1}
- end;
-eval(Error) ->
- {Error, -1}.
-
+%%--------------------------------------------------------------------
%%% io_request, handle io requests from the user process,
%%% Note, this is not the real I/O-protocol, but the mockup version
%%% used between edlin and a user_driver. The protocol tags are
@@ -506,53 +489,130 @@ bin_to_list(L) when is_list(L) ->
bin_to_list(I) when is_integer(I) ->
I.
+
+%%--------------------------------------------------------------------
start_shell(ConnectionHandler, State) ->
- Shell = State#state.shell,
- ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler,
- [peer, user]),
- ShellFun = case is_function(Shell) of
- true ->
- User = proplists:get_value(user, ConnectionInfo),
- case erlang:fun_info(Shell, arity) of
- {arity, 1} ->
- fun() -> Shell(User) end;
- {arity, 2} ->
- {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
- fun() -> Shell(User, PeerAddr) end;
- _ ->
- Shell
- end;
- _ ->
- Shell
- end,
- Echo = get_echo(State#state.pty),
- Group = group:start(self(), ShellFun, [{echo, Echo}]),
- State#state{group = Group, buf = empty_buf()}.
-
-start_shell(_ConnectionHandler, Cmd, #state{exec={M, F, A}} = State) ->
- Group = group:start(self(), {M, F, A++[Cmd]}, [{echo, false}]),
- State#state{group = Group, buf = empty_buf()};
-start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function(Shell) ->
-
- ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler,
- [peer, user]),
- User = proplists:get_value(user, ConnectionInfo),
- ShellFun =
- case erlang:fun_info(Shell, arity) of
- {arity, 1} ->
- fun() -> Shell(Cmd) end;
- {arity, 2} ->
- fun() -> Shell(Cmd, User) end;
- {arity, 3} ->
- {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
- fun() -> Shell(Cmd, User, PeerAddr) end;
- _ ->
- Shell
- end,
- Echo = get_echo(State#state.pty),
- Group = group:start(self(), ShellFun, [{echo,Echo}]),
- State#state{group = Group, buf = empty_buf()}.
+ ShellSpawner =
+ case State#state.shell of
+ Shell when is_function(Shell, 1) ->
+ [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]),
+ fun() -> Shell(User) end;
+ Shell when is_function(Shell, 2) ->
+ ConnectionInfo =
+ ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]),
+ User = proplists:get_value(user, ConnectionInfo),
+ {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
+ fun() -> Shell(User, PeerAddr) end;
+ {_,_,_} = Shell ->
+ Shell
+ end,
+ State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]),
+ buf = empty_buf()}.
+
+%%--------------------------------------------------------------------
+start_exec_shell(ConnectionHandler, Cmd, State) ->
+ ExecShellSpawner =
+ case State#state.exec of
+ ExecShell when is_function(ExecShell, 1) ->
+ fun() -> ExecShell(Cmd) end;
+ ExecShell when is_function(ExecShell, 2) ->
+ [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]),
+ fun() -> ExecShell(Cmd, User) end;
+ ExecShell when is_function(ExecShell, 3) ->
+ ConnectionInfo =
+ ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]),
+ User = proplists:get_value(user, ConnectionInfo),
+ {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
+ fun() -> ExecShell(Cmd, User, PeerAddr) end;
+ {M,F,A} ->
+ {M, F, A++[Cmd]}
+ end,
+ State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]),
+ buf = empty_buf()}.
+
+%%--------------------------------------------------------------------
+exec_in_erlang_default_shell(Cmd) ->
+ case eval(parse(scan(Cmd))) of
+ {ok, Term} ->
+ {io_lib:format("~p\n", [Term]), 0, 0};
+ {error, Error} when is_atom(Error) ->
+ {io_lib:format("Error in ~p: ~p\n", [Cmd,Error]), -1, 1};
+ _ ->
+ {io_lib:format("Error: ~p\n", [Cmd]), -1, 1}
+ end.
+
+scan(Cmd) ->
+ erl_scan:string(Cmd).
+
+parse({ok, Tokens, _}) ->
+ erl_parse:parse_exprs(Tokens);
+parse(Error) ->
+ Error.
+eval({ok, Expr_list}) ->
+ case (catch erl_eval:exprs(Expr_list,
+ erl_eval:new_bindings())) of
+ {value, Value, _NewBindings} ->
+ {ok, Value};
+ {'EXIT', {Error, _}} ->
+ {error, Error};
+ {error, Error} ->
+ {error, Error};
+ Error ->
+ {error, Error}
+ end;
+eval({error,Error}) ->
+ {error, Error};
+eval(Error) ->
+ {error, Error}.
+
+%%--------------------------------------------------------------------
+exec_direct(ConnectionHandler, ExecSpec, Cmd) ->
+ try
+ case ExecSpec of
+ _ when is_function(ExecSpec, 1) ->
+ ExecSpec(Cmd);
+ _ when is_function(ExecSpec, 2) ->
+ [{user,User}] = ssh_connection_handler:connection_info(ConnectionHandler, [user]),
+ ExecSpec(Cmd, User);
+ _ when is_function(ExecSpec, 3) ->
+ ConnectionInfo =
+ ssh_connection_handler:connection_info(ConnectionHandler, [peer, user]),
+ User = proplists:get_value(user, ConnectionInfo),
+ {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
+ ExecSpec(Cmd, User, PeerAddr)
+ end
+ of
+ Reply ->
+ return_direct_exec_reply(Reply, Cmd)
+ catch
+ C:Error ->
+ {io_lib:format("Error in \"~s\": ~p ~p~n", [Cmd,C,Error]), -1, 1}
+ end.
+
+
+
+return_direct_exec_reply(Reply, Cmd) ->
+ case fmt_exec_repl(Reply) of
+ {ok,S} ->
+ {S, 0, 0};
+ {error,S} ->
+ {io_lib:format("Error in \"~s\": ~s~n", [Cmd,S]), -1, 1}
+ end.
+
+fmt_exec_repl({T,A}) when T==ok ; T==error ->
+ try
+ {T, io_lib:format("~s",[A])}
+ catch
+ error:badarg ->
+ {T, io_lib:format("~p", [A])};
+ C:Err ->
+ {error, io_lib:format("~p:~p~n",[C,Err])}
+ end;
+fmt_exec_repl(Other) ->
+ {error, io_lib:format("Bad exec-plugin return: ~p",[Other])}.
+
+%%--------------------------------------------------------------------
% Pty can be undefined if the client never sets any pty options before
% starting the shell.
get_echo(undefined) ->
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 1e10f72956..c05293d1ae 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -275,10 +275,12 @@ default(server) ->
class => user_options
},
- {exec, def} => % FIXME: need some archeology....
+ {exec, def} =>
#{default => undefined,
- chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F);
- (V) -> is_function(V)
+ chk => fun({direct, V}) -> check_function1(V) orelse check_function2(V) orelse check_function3(V);
+ %% Compatibility (undocumented):
+ ({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);
+ (V) -> check_function1(V) orelse check_function2(V) orelse check_function3(V)
end,
class => user_options
},
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index 9587c0c251..257f2f70d7 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -50,6 +50,13 @@ all() ->
start_shell,
start_shell_exec,
start_shell_exec_fun,
+ start_shell_exec_fun2,
+ start_shell_exec_fun3,
+ start_shell_exec_direct_fun,
+ start_shell_exec_direct_fun2,
+ start_shell_exec_direct_fun3,
+ start_shell_exec_direct_fun1_error,
+ start_shell_exec_direct_fun1_error_type,
start_shell_sock_exec_fun,
start_shell_sock_daemon_exec,
connect_sock_not_tcp,
@@ -522,7 +529,7 @@ start_shell_exec(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
- {exec, {?MODULE,ssh_exec,[]}} ]),
+ {exec, {?MODULE,ssh_exec_echo,[]}} ]),
ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -535,7 +542,7 @@ start_shell_exec(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -618,10 +625,49 @@ exec_erlang_term_non_default_shell(Config) when is_list(Config) ->
TestResult.
%%--------------------------------------------------------------------
-start_shell_exec_fun() ->
- [{doc, "start shell to exec command"}].
+start_shell_exec_fun(Config) ->
+ do_start_shell_exec_fun(fun ssh_exec_echo/1,
+ "testing", <<"echo testing\r\n">>, 0,
+ Config).
+
+start_shell_exec_fun2(Config) ->
+ do_start_shell_exec_fun(fun ssh_exec_echo/2,
+ "testing", <<"echo foo testing\r\n">>, 0,
+ Config).
+
+start_shell_exec_fun3(Config) ->
+ do_start_shell_exec_fun(fun ssh_exec_echo/3,
+ "testing", <<"echo foo testing\r\n">>, 0,
+ Config).
+
+start_shell_exec_direct_fun(Config) ->
+ do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/1},
+ "testing", <<"echo testing\n">>, 0,
+ Config).
+
+start_shell_exec_direct_fun2(Config) ->
+ do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/2},
+ "testing", <<"echo foo testing">>, 0,
+ Config).
+
+start_shell_exec_direct_fun3(Config) ->
+ do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/3},
+ "testing", <<"echo foo testing">>, 0,
+ Config).
+
+start_shell_exec_direct_fun1_error(Config) ->
+ do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return/1},
+ "testing", <<"Error in \"testing\": {bad}\n">>, 1,
+ Config).
+
+start_shell_exec_direct_fun1_error_type(Config) ->
+ do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return_type/1},
+ "testing", <<"Error in \"testing\": Bad exec-plugin return: very_bad\n">>, 1,
+ Config).
+
+
-start_shell_exec_fun(Config) when is_list(Config) ->
+do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
file:make_dir(UserDir),
@@ -629,7 +675,7 @@ start_shell_exec_fun(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
- {exec, fun ssh_exec/1}]),
+ {exec, Fun}]),
ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -639,14 +685,19 @@ start_shell_exec_fun(Config) when is_list(Config) ->
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId0,
- "testing", infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0, Command, infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, ExpectType, Expect}} ->
ok
after 5000 ->
- ct:fail("Exec Timeout")
+ receive
+ Other ->
+ ct:pal("Received other:~n~p",[Other]),
+ ct:fail("Unexpected response")
+ after 0 ->
+ ct:fail("Exec Timeout")
+ end
end,
ssh:close(ConnectionRef),
@@ -664,7 +715,7 @@ start_shell_sock_exec_fun(Config) when is_list(Config) ->
{Pid, HostD, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
- {exec, fun ssh_exec/1}]),
+ {exec, fun ssh_exec_echo/1}]),
Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(HostD)),
{ok, Sock} = ssh_test_lib:gen_tcp_connect(Host, Port, [{active,false}]),
@@ -680,7 +731,7 @@ start_shell_sock_exec_fun(Config) when is_list(Config) ->
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -704,7 +755,7 @@ start_shell_sock_daemon_exec(Config) ->
{ok, _Pid} = ssh:daemon(Ss, [{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
- {exec, fun ssh_exec/1}])
+ {exec, fun ssh_exec_echo/1}])
end),
{ok,Sc} = gen_tcp:accept(Sl),
{ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
@@ -719,7 +770,7 @@ start_shell_sock_daemon_exec(Config) ->
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -830,7 +881,7 @@ stop_listener(Config) when is_list(Config) ->
{Pid0, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
- {exec, fun ssh_exec/1}]),
+ {exec, fun ssh_exec_echo/1}]),
ConnectionRef0 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -850,7 +901,7 @@ stop_listener(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef0, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\r\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -859,7 +910,7 @@ stop_listener(Config) when is_list(Config) ->
case ssh_test_lib:daemon(Port, [{system_dir, SysDir},
{user_dir, UserDir},
{password, "potatis"},
- {exec, fun ssh_exec/1}]) of
+ {exec, fun ssh_exec_echo/1}]) of
{Pid1, Host, Port} ->
ConnectionRef1 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -1070,7 +1121,22 @@ start_our_shell(_User, _Peer) ->
%% Don't actually loop, just exit
end).
-ssh_exec(Cmd) ->
+
+ssh_exec_echo(Cmd) ->
spawn(fun() ->
- io:format(Cmd ++ "\n")
+ io:format("echo "++Cmd ++ "\n")
end).
+
+ssh_exec_echo(Cmd, User) ->
+ spawn(fun() ->
+ io:format(io_lib:format("echo ~s ~s\n",[User,Cmd]))
+ end).
+ssh_exec_echo(Cmd, User, _PeerAddr) ->
+ ssh_exec_echo(Cmd,User).
+
+ssh_exec_direct_echo(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])}.
+ssh_exec_direct_echo(Cmd, User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])}.
+ssh_exec_direct_echo(Cmd, User, _PeerAddr) -> ssh_exec_direct_echo(Cmd,User).
+
+ssh_exec_direct_echo_error_return(_Cmd) -> {error, {bad}}.
+ssh_exec_direct_echo_error_return_type(_Cmd) -> very_bad.
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 7bfe477a11..2a3785dc27 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -83,6 +83,8 @@
</item>
<item><seealso marker="#is_element/2"><c>is_element/2</c></seealso>
</item>
+ <item><seealso marker="#is_empty/1"><c>is_empty/1</c></seealso>
+ </item>
<item><seealso marker="#is_set/1"><c>is_set/1</c></seealso>
</item>
<item><seealso marker="#is_subset/2"><c>is_subset/2</c></seealso>
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 7b590932e4..2d891d7a5a 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -142,6 +142,15 @@
</func>
<func>
+ <name name="is_empty" arity="1"/>
+ <fsummary>Test for empty set.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an empty set,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="is_set" arity="1"/>
<fsummary>Test for an <c>Ordset</c>.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 4934bed365..1ed96ddc3f 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -140,6 +140,15 @@
</func>
<func>
+ <name name="is_empty" arity="1"/>
+ <fsummary>Test for empty set.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="is_set" arity="1"/>
<fsummary>Test for a <c>Set</c>.</fsummary>
<desc>
diff --git a/lib/stdlib/src/erl_posix_msg.erl b/lib/stdlib/src/erl_posix_msg.erl
index bfafca1ff7..8959fea498 100644
--- a/lib/stdlib/src/erl_posix_msg.erl
+++ b/lib/stdlib/src/erl_posix_msg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,6 +64,7 @@ message_1(eduppkg) -> <<"duplicate package name">>;
message_1(eexist) -> <<"file already exists">>;
message_1(efault) -> <<"bad address in system call argument">>;
message_1(efbig) -> <<"file too large">>;
+message_1(eftype) -> <<"EFTYPE">>;
message_1(ehostdown) -> <<"host is down">>;
message_1(ehostunreach) -> <<"host is unreachable">>;
message_1(eidrm) -> <<"identifier removed">>;
@@ -115,6 +116,7 @@ message_1(enopkg) -> <<"package not installed">>;
message_1(enoprotoopt) -> <<"bad proocol option">>;
message_1(enospc) -> <<"no space left on device">>;
message_1(enosr) -> <<"out of stream resources or not a stream device">>;
+message_1(enostr) -> <<"not a stream">>;
message_1(enosym) -> <<"unresolved symbol name">>;
message_1(enosys) -> <<"function not implemented">>;
message_1(enotblk) -> <<"block device required">>;
@@ -128,6 +130,7 @@ message_1(enotty) -> <<"inappropriate device for ioctl">>;
message_1(enotuniq) -> <<"name not unique on network">>;
message_1(enxio) -> <<"no such device or address">>;
message_1(eopnotsupp) -> <<"operation not supported on socket">>;
+message_1(eoverflow) -> <<"offset too large for file system">>;
message_1(eperm) -> <<"not owner">>;
message_1(epfnosupport) -> <<"protocol family not supported">>;
message_1(epipe) -> <<"broken pipe">>;
@@ -167,4 +170,6 @@ message_1(ewouldblock) -> <<"operation would block">>;
message_1(exdev) -> <<"cross-domain link">>;
message_1(exfull) -> <<"message tables full">>;
message_1(nxdomain) -> <<"non-existing domain">>;
+message_1(exbadport) -> <<"inet_drv bad port state">>;
+message_1(exbadseq) -> <<"inet_drv bad request sequence">>;
message_1(_) -> <<"unknown POSIX error">>.
diff --git a/lib/stdlib/src/ordsets.erl b/lib/stdlib/src/ordsets.erl
index 569407f5ef..939e147ad8 100644
--- a/lib/stdlib/src/ordsets.erl
+++ b/lib/stdlib/src/ordsets.erl
@@ -19,7 +19,7 @@
-module(ordsets).
--export([new/0,is_set/1,size/1,to_list/1,from_list/1]).
+-export([new/0,is_set/1,size/1,is_empty/1,to_list/1,from_list/1]).
-export([is_element/2,add_element/2,del_element/2]).
-export([union/2,union/1,intersection/2,intersection/1]).
-export([is_disjoint/2]).
@@ -60,6 +60,13 @@ is_set([], _) -> true.
size(S) -> length(S).
+%% is_empty(OrdSet) -> boolean().
+%% Return 'true' if OrdSet is an empty set, otherwise 'false'.
+-spec is_empty(Ordset) -> boolean() when
+ Ordset :: ordset(_).
+
+is_empty(S) -> S=:=[].
+
%% to_list(OrdSet) -> [Elem].
%% Return the elements in OrdSet as a list.
diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl
index c65a13b22e..ac0fc80526 100644
--- a/lib/stdlib/src/sets.erl
+++ b/lib/stdlib/src/sets.erl
@@ -37,7 +37,7 @@
-module(sets).
%% Standard interface.
--export([new/0,is_set/1,size/1,to_list/1,from_list/1]).
+-export([new/0,is_set/1,size/1,is_empty/1,to_list/1,from_list/1]).
-export([is_element/2,add_element/2,del_element/2]).
-export([union/2,union/1,intersection/2,intersection/1]).
-export([is_disjoint/2]).
@@ -96,6 +96,12 @@ is_set(_) -> false.
Set :: set().
size(S) -> S#set.size.
+%% is_empty(Set) -> boolean().
+%% Return 'true' if Set is an empty set, otherwise 'false'.
+-spec is_empty(Set) -> boolean() when
+ Set :: set().
+is_empty(S) -> S#set.size=:=0.
+
%% to_list(Set) -> [Elem].
%% Return the elements in Set as a list.
-spec to_list(Set) -> List when
diff --git a/lib/stdlib/test/sets_SUITE.erl b/lib/stdlib/test/sets_SUITE.erl
index bec38000b2..7066d07e19 100644
--- a/lib/stdlib/test/sets_SUITE.erl
+++ b/lib/stdlib/test/sets_SUITE.erl
@@ -28,7 +28,7 @@
init_per_testcase/2,end_per_testcase/2,
create/1,add_element/1,del_element/1,
subtract/1,intersection/1,union/1,is_subset/1,
- is_set/1,fold/1,filter/1,
+ is_set/1,is_empty/1,fold/1,filter/1,
take_smallest/1,take_largest/1, iterate/1]).
-include_lib("common_test/include/ct.hrl").
@@ -48,7 +48,7 @@ suite() ->
all() ->
[create, add_element, del_element, subtract,
intersection, union, is_subset, is_set, fold, filter,
- take_smallest, take_largest, iterate].
+ take_smallest, take_largest, iterate, is_empty].
groups() ->
[].
@@ -345,6 +345,17 @@ is_set_1(M) ->
false = M(is_set, {}),
M(empty, []).
+is_empty(Config) when is_list(Config) ->
+ test_all(fun is_empty_1/1).
+
+is_empty_1(M) ->
+ S = M(from_list, [blurf]),
+ Empty = M(empty, []),
+
+ true = M(is_empty, Empty),
+ false = M(is_empty, S),
+ M(empty, []).
+
fold(Config) when is_list(Config) ->
test_all([{0,71},{125,129},{254,259},{510,513},{1023,1025},{9999,10001}],
fun fold_1/2).
diff --git a/lib/stdlib/test/sets_test_lib.erl b/lib/stdlib/test/sets_test_lib.erl
index 9f153822a2..93d027704b 100644
--- a/lib/stdlib/test/sets_test_lib.erl
+++ b/lib/stdlib/test/sets_test_lib.erl
@@ -32,7 +32,7 @@ new(Mod, Eq) ->
(from_list, L) -> Mod:from_list(L);
(intersection, {S1,S2}) -> intersection(Mod, Eq, S1, S2);
(intersection, Ss) -> intersection(Mod, Eq, Ss);
- (is_empty, S) -> is_empty(Mod, S);
+ (is_empty, S) -> Mod:is_empty(S);
(is_set, S) -> Mod:is_set(S);
(is_subset, {S,Set}) -> is_subset(Mod, Eq, S, Set);
(iterator, S) -> Mod:iterator(S);
@@ -56,7 +56,7 @@ singleton(Mod, E) ->
add_element(Mod, El, S0) ->
S = Mod:add_element(El, S0),
true = Mod:is_element(El, S),
- false = is_empty(Mod, S),
+ false = Mod:is_empty(S),
true = Mod:is_set(S),
S.
@@ -66,17 +66,10 @@ del_element(Mod, El, S0) ->
true = Mod:is_set(S),
S.
-is_empty(Mod, S) ->
- true = Mod:is_set(S),
- case erlang:function_exported(Mod, is_empty, 1) of
- true -> Mod:is_empty(S);
- false -> Mod:size(S) == 0
- end.
-
intersection(Mod, Equal, S1, S2) ->
S = Mod:intersection(S1, S2),
true = Equal(S, Mod:intersection(S2, S1)),
- Disjoint = is_empty(Mod, S),
+ Disjoint = Mod:is_empty(S),
Disjoint = Mod:is_disjoint(S1, S2),
Disjoint = Mod:is_disjoint(S2, S1),
S.
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index 45c6cb3f0f..b88f368746 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -884,7 +884,6 @@ resulting regexp is surrounded by \\_< and \\_>."
"alloc_sizes"
"append"
"append_element"
- "await_proc_exit"
"bump_reductions"
"call_on_load_function"
"cancel_timer"
diff --git a/lib/xmerl/src/xmerl_xsd.erl b/lib/xmerl/src/xmerl_xsd.erl
index a89b3159ec..d727084175 100644
--- a/lib/xmerl/src/xmerl_xsd.erl
+++ b/lib/xmerl/src/xmerl_xsd.erl
@@ -18,7 +18,7 @@
%% %CopyrightEnd%
%%
-%% @doc Interface module for XML Schema vlidation.
+%% @doc Interface module for XML Schema validation.
%% It handles the W3.org
%% <a href="http://www.w3.org/XML/Schema#dev">specifications</a>
%% of XML Schema second edition 28 october 2004. For an introduction to