diff options
Diffstat (limited to 'lib')
210 files changed, 6273 insertions, 3102 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 7137dbc036..8cb16d8f09 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -1414,6 +1414,17 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, false -> true end; + [Pat0, Pat1] -> % binary comprehension + case cerl:is_c_cons(Pat0) of + true -> + not (cerl:is_c_var(cerl:cons_hd(Pat0)) andalso + cerl:is_c_var(cerl:cons_tl(Pat0)) andalso + cerl:is_c_var(Pat1) andalso + cerl:is_literal(Guard) andalso + (cerl:concrete(Guard) =:= true)); + false -> + true + end; _ -> true end; false -> @@ -2915,7 +2926,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, {Warn, Msg} = case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of error -> {true, {unused_fun, []}}; - {ok, {_M, F, A}} = MFA -> + {ok, {_M, F, A} = MFA} -> {not sets:is_element(MFA, NoWarnUnused), {unused_fun, [F, A]}} end, diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index c45615d670..65c2ff76bb 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -1684,11 +1684,14 @@ solve_scc(SCC, Map, State, TryingUnit) -> true -> ?debug("SCC ~w reached fixpoint\n", [SCC]), NewTypes = unsafe_lookup_type_list(Funs, Map2), - case lists:all(fun(T) -> t_is_none(t_fun_range(T)) end, NewTypes) + case erl_types:any_none([t_fun_range(T) || T <- NewTypes]) andalso TryingUnit =:= false of true -> - UnitTypes = [t_fun(state__fun_arity(F, State), t_unit()) - || F <- Funs], + UnitTypes = + [case t_is_none(t_fun_range(T)) of + false -> T; + true -> t_fun(t_fun_args(T), t_unit()) + end || T <- NewTypes], Map3 = enter_type_lists(Funs, UnitTypes, Map2), solve_scc(SCC, Map3, State, true); false -> diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/asn1 b/lib/dialyzer/test/r9c_SUITE_data/results/asn1 index ac83366bc8..571e562d0d 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/asn1 +++ b/lib/dialyzer/test/r9c_SUITE_data/results/asn1 @@ -2,7 +2,7 @@ asn1ct.erl:1500: The variable Err can never match since previous clauses completely covered the type #type{} asn1ct.erl:1596: The variable _ can never match since previous clauses completely covered the type 'ber_bin_v2' asn1ct.erl:1673: The pattern 'all' can never match the type 'asn1_module' | 'exclusive_decode' | 'partial_decode' -asn1ct.erl:672: The pattern <{'false', Result}, _, _> can never match the type <{'true','true'},atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()],[any()]> +asn1ct.erl:672: The pattern <{'false', Result}, _, _> can never match the type <{'true','true'},atom() | binary() | [atom() | [any()] | char()],[any()]> asn1ct.erl:909: Guard test is_atom(Ext::[49 | 97 | 98 | 100 | 110 | 115]) can never succeed asn1ct_check.erl:1698: The pattern {'error', _} can never match the type [any()] asn1ct_check.erl:2733: The pattern {'type', Tag, _, _, _, _} can never match the type 'ASN1_OPEN_TYPE' | {_,_} | {'fixedtypevaluefield',_,_} diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/inets b/lib/dialyzer/test/r9c_SUITE_data/results/inets index fd5e36a3cd..6b16dba2ff 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/inets +++ b/lib/dialyzer/test/r9c_SUITE_data/results/inets @@ -7,9 +7,6 @@ http_lib.erl:286: The call http_lib:close('ip_comm' | {'ssl',_},any()) will neve http_lib.erl:424: The variable _ can never match since previous clauses completely covered the type any() http_lib.erl:438: The variable _ can never match since previous clauses completely covered the type any() http_lib.erl:99: Function getHeaderValue/2 will never be called -httpc_handler.erl:322: Function status_continue/2 has no local return -httpc_handler.erl:37: Function init_connection/2 has no local return -httpc_handler.erl:65: Function next_response_with_request/2 has no local return httpc_handler.erl:660: Function exit_session_ok/2 has no local return httpc_manager.erl:145: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} httpc_manager.erl:160: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} @@ -27,7 +24,7 @@ httpd_manager.erl:933: Function acceptor_status/1 will never be called httpd_request_handler.erl:374: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 66 | 98 | 100 | 103 | 105 | 111 | 116 | 121,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) httpd_request_handler.erl:378: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) httpd_request_handler.erl:401: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any()) -httpd_request_handler.erl:644: The call lists:reverse(Fields0::{'error',_} | {'ok',[[any()]]}) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) +httpd_request_handler.erl:644: The call lists:reverse(Fields0::{'error',_} | {'ok',_}) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) httpd_request_handler.erl:645: Function will never be called httpd_sup.erl:63: The variable Else can never match since previous clauses completely covered the type {'error',_} | {'ok',[any()],_,_} httpd_sup.erl:88: The pattern {'error', Reason} can never match the type {'ok',_,_} @@ -38,17 +35,17 @@ mod_auth_plain.erl:100: The variable _ can never match since previous clauses co mod_auth_plain.erl:159: The variable _ can never match since previous clauses completely covered the type [any()] mod_auth_plain.erl:83: The variable O can never match since previous clauses completely covered the type [any()] mod_cgi.erl:372: The pattern {'http_response', NewAccResponse} can never match the type 'ok' -mod_dir.erl:101: The call lists:flatten(nonempty_improper_list(atom() | binary() | [any()] | char(),atom())) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) +mod_dir.erl:101: The call lists:flatten(nonempty_improper_list(atom() | [any()] | char(),atom())) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) mod_dir.erl:72: The pattern {'error', Reason} can never match the type {'ok',[[[any()] | char()],...]} -mod_get.erl:135: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | binary() | [any()] | char()]> -mod_head.erl:80: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]> +mod_get.erl:135: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> +mod_head.erl:80: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_htaccess.erl:460: The pattern {'error', BadData} can never match the type {'ok',_} -mod_include.erl:193: The pattern {_, Name, {[], []}} can never match the type {[any()],[any()],maybe_improper_list()} -mod_include.erl:195: The pattern {_, Name, {PathInfo, []}} can never match the type {[any()],[any()],maybe_improper_list()} -mod_include.erl:197: The pattern {_, Name, {PathInfo, QueryString}} can never match the type {[any()],[any()],maybe_improper_list()} -mod_include.erl:201: The variable Gurka can never match since previous clauses completely covered the type {[any()],[any()],maybe_improper_list()} -mod_include.erl:692: The pattern <{'read', Reason}, Info, Path> can never match the type <{'open',atom()},#mod{},atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]> -mod_include.erl:706: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]> +mod_include.erl:193: The pattern {_, Name, {[], []}} can never match the type {[any()],[any()],string()} +mod_include.erl:195: The pattern {_, Name, {PathInfo, []}} can never match the type {[any()],[any()],string()} +mod_include.erl:197: The pattern {_, Name, {PathInfo, QueryString}} can never match the type {[any()],[any()],string()} +mod_include.erl:201: The variable Gurka can never match since previous clauses completely covered the type {[any()],[any()],string()} +mod_include.erl:692: The pattern <{'read', Reason}, Info, Path> can never match the type <{'open',atom()},#mod{},atom() | binary() | [atom() | [any()] | char()]> +mod_include.erl:706: The pattern <{'enfile', _}, _Info, Path> can never match the type <atom(),#mod{},atom() | binary() | [atom() | [any()] | char()]> mod_include.erl:716: Function read_error/3 will never be called mod_include.erl:719: Function read_error/4 will never be called mod_security_server.erl:386: The variable O can never match since previous clauses completely covered the type [any()] diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/mnesia b/lib/dialyzer/test/r9c_SUITE_data/results/mnesia index e199581a0e..b0f4d12ae5 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/mnesia +++ b/lib/dialyzer/test/r9c_SUITE_data/results/mnesia @@ -6,6 +6,9 @@ mnesia_bup.erl:111: The created fun has no local return mnesia_bup.erl:574: Function fallback_receiver/2 has no local return mnesia_bup.erl:967: Function uninstall_fallback_master/2 has no local return mnesia_checkpoint.erl:1014: The variable Error can never match since previous clauses completely covered the type {'ok',#checkpoint_args{nodes::[any()],retainers::[any(),...]}} +mnesia_checkpoint.erl:1209: Function system_continue/3 has no local return +mnesia_checkpoint.erl:792: Function retainer_loop/1 has no local return +mnesia_checkpoint.erl:894: The call sys:handle_system_msg(Msg::any(),From::any(),'no_parent','mnesia_checkpoint',[],Cp::#checkpoint_args{}) breaks the contract (Msg,From,Parent,Module,Debug,Misc) -> Void when is_subtype(Msg,term()), is_subtype(From,{pid(),Tag::_}), is_subtype(Parent,pid()), is_subtype(Module,module()), is_subtype(Debug,[dbg_opt()]), is_subtype(Misc,term()), is_subtype(Void,term()) mnesia_controller.erl:1666: The variable Tab can never match since previous clauses completely covered the type [any()] mnesia_controller.erl:1679: The pattern {'stop', Reason, Reply, State2} can never match the type {'noreply',_} | {'reply',_,_} | {'stop','shutdown',#state{}} mnesia_controller.erl:1685: The pattern {'noreply', State2, _Timeout} can never match the type {'reply',_,_} @@ -15,6 +18,7 @@ mnesia_frag.erl:294: The call mnesia_frag:remote_collect(Ref::reference(),{'erro mnesia_frag.erl:304: The call mnesia_frag:remote_collect(Ref::reference(),{'error',{'node_not_running',_}},[],OldSelectFun::fun(() -> [any()])) will never return since it differs in the 2nd argument from the success typing arguments: (reference(),'ok',[any()],fun(() -> [any()])) mnesia_frag.erl:312: The call mnesia_frag:remote_collect(Ref::reference(),LocalRes::{'error',_},[],OldSelectFun::fun(() -> [any()])) will never return since it differs in the 2nd argument from the success typing arguments: (reference(),'ok',[any()],fun(() -> [any()])) mnesia_index.erl:52: The call mnesia_lib:other_val(Var::{_,'commit_work' | 'index' | 'setorbag' | 'storage_type' | {'index',_}},_ReASoN_::any()) will never return since it differs in the 1st argument from the success typing arguments: ({_,'active_replicas' | 'where_to_read' | 'where_to_write'},any()) +mnesia_lib.erl:1028: The pattern {'EXIT', Reason} can never match the type [any()] | {'error',_} mnesia_lib.erl:957: The pattern {'ok', {0, _}} can never match the type 'eof' | {'error',atom()} | {'ok',binary() | string()} mnesia_lib.erl:959: The pattern {'ok', {_, Bin}} can never match the type 'eof' | {'error',atom()} | {'ok',binary() | string()} mnesia_loader.erl:36: The call mnesia_lib:other_val(Var::{_,'access_mode' | 'cstruct' | 'db_nodes' | 'setorbag' | 'snmp' | 'storage_type'},Reason::any()) will never return since it differs in the 1st argument from the success typing arguments: ({_,'active_replicas' | 'where_to_read' | 'where_to_write'},any()) @@ -30,5 +34,6 @@ mnesia_schema.erl:1258: Guard test FromS::'disc_copies' | 'disc_only_copies' | ' mnesia_schema.erl:1639: The pattern {'false', 'mandatory'} can never match the type {'false','optional'} mnesia_schema.erl:2434: The variable Reason can never match since previous clauses completely covered the type {'error',_} | {'ok',_} mnesia_schema.erl:451: Guard test UseDirAnyway::'false' == 'true' can never succeed +mnesia_text.erl:180: The variable T can never match since previous clauses completely covered the type {'error',{integer(),atom() | tuple(),_}} | {'ok',_} mnesia_tm.erl:1522: Function commit_participant/5 has no local return mnesia_tm.erl:2169: Function system_terminate/4 has no local return diff --git a/lib/dialyzer/test/race_SUITE_data/results/extract_translations b/lib/dialyzer/test/race_SUITE_data/results/extract_translations index f7d5abc6f5..62aa1aa511 100644 --- a/lib/dialyzer/test/race_SUITE_data/results/extract_translations +++ b/lib/dialyzer/test/race_SUITE_data/results/extract_translations @@ -1,5 +1,5 @@ -extract_translations.erl:140: The call ets:insert('files',{atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('files',File::atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]) call in extract_translations.erl on line 135 +extract_translations.erl:140: The call ets:insert('files',{atom() | binary() | [atom() | [any()] | char()]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('files',File::atom() | binary() | [atom() | [any()] | char()]) call in extract_translations.erl on line 135 extract_translations.erl:146: The call ets:insert('translations',{_,[]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('translations',Str::any()) call in extract_translations.erl on line 126 -extract_translations.erl:152: The call ets:insert('files',{atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('files',File::atom() | binary() | [atom() | binary() | [atom() | binary() | [any()] | char()] | char()]) call in extract_translations.erl on line 148 +extract_translations.erl:152: The call ets:insert('files',{atom() | binary() | [atom() | [any()] | char()]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('files',File::atom() | binary() | [atom() | [any()] | char()]) call in extract_translations.erl on line 148 extract_translations.erl:154: The call ets:insert('translations',{_,[]}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('translations',Str::any()) call in extract_translations.erl on line 126 diff --git a/lib/dialyzer/test/small_SUITE_data/results/flatten b/lib/dialyzer/test/small_SUITE_data/results/flatten index 4571214e49..8aa44dd002 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/flatten +++ b/lib/dialyzer/test/small_SUITE_data/results/flatten @@ -1,2 +1,2 @@ -flatten.erl:17: The call lists:flatten(nonempty_improper_list(atom() | binary() | [any()] | char(),atom())) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) +flatten.erl:17: The call lists:flatten(nonempty_improper_list(atom() | [any()] | char(),atom())) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) diff --git a/lib/dialyzer/test/small_SUITE_data/src/binary_lc_bug.erl b/lib/dialyzer/test/small_SUITE_data/src/binary_lc_bug.erl new file mode 100644 index 0000000000..c1e82bfa59 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/binary_lc_bug.erl @@ -0,0 +1,8 @@ +-module(test). + +-export([bin_compr/0]). + +bin_compr() -> +% [ 0 || {N, V} <- [{a, b}] ]. % Works ok + << <<>> || {A, B} <- [{a, b}] >>. % Complains +% << <<>> || X <- [{a, b}] >>. % Works ok diff --git a/lib/dialyzer/test/small_SUITE_data/src/codec_can.erl b/lib/dialyzer/test/small_SUITE_data/src/codec_can.erl new file mode 100644 index 0000000000..8abf872b37 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/codec_can.erl @@ -0,0 +1,35 @@ +%%--------------------------------------------------------------------- +%% From: Peer Stritzinger +%% Date: 1 May 2011 +%% Subject: Dialyzer v2.2.0 crash +%% +%% Binaries of the form <<_:N,_:_*M>> in specs resulted in a crash: +%% dialyzer: Analysis failed with error: {{case_clause,8}, +%% [{erl_types,t_form_to_string,1}, +%% {erl_types,t_form_to_string,1}, +%% {dialyzer_contracts,contract_to_string_1,1}, +%% {dialyzer_contracts,extra_contract_warning,6}, +%% {dialyzer_contracts,picky_contract_check,7}, +%% because function erl_types:t_form_to_string/1 was not the inverse +%% of erl_types:t_to_string/2. +%% +%% Fixed on the same date and send to OTP for inclusion. +%%--------------------------------------------------------------------- +-module(codec_can). + +-export([recv/3, decode/1]). + +-record(can_pkt, {id, data :: binary(), timestamp}). + +-type can_pkt() :: #can_pkt{}. +-type channel() :: atom() | pid() | {atom(),_}. + +-spec recv(<<_:64,_:_*8>>, fun((can_pkt()) -> R), channel()) -> R. +recv(Packet, Fun, Chan) -> + #can_pkt{id = Can_id, data = Can_data} = P = decode(Packet), + Fun(P). + +-spec decode(<<_:64,_:_*8>>) -> #can_pkt{id::<<_:11>>,timestamp::char()}. +decode(<<_:12, Len:4, Timestamp:16, 0:3, Id:11/bitstring, 0:18, + Data:Len/binary, _/binary>>) -> + #can_pkt{id = Id, data = Data, timestamp = Timestamp}. diff --git a/lib/dialyzer/test/small_SUITE_data/src/list_to_bitstring.erl b/lib/dialyzer/test/small_SUITE_data/src/list_to_bitstring.erl new file mode 100644 index 0000000000..2da708cb15 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/list_to_bitstring.erl @@ -0,0 +1,21 @@ +%%===================================================================== +%% From: Ken Robinson +%% Date: 28/04/2011, 17:26 +%% +%% Program that produced borus "Function has no local return" warnings +%% due to erlang:list_to_bitstring/1 having erroneous hard coded type +%% information, namely accepting iolist() instead of bitstrlist(). +%% Fixed 29/04/2011. +%%===================================================================== + +-module(list_to_bitstring). + +-export([l2bs/0, l2bs_ok/0]). + +%% This function was producing a warning +l2bs() -> + erlang:list_to_bitstring([<<42>>, <<42:13>>]). + +%% while this one was ok. +l2bs_ok() -> + erlang:list_to_bitstring([<<42>>, <<42,42>>]). diff --git a/lib/dialyzer/test/small_SUITE_data/src/no_return_bug.erl b/lib/dialyzer/test/small_SUITE_data/src/no_return_bug.erl new file mode 100644 index 0000000000..5c24902590 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/no_return_bug.erl @@ -0,0 +1,42 @@ +%% Dialyzer couldn't infer that monitor_diskspace would go in an infinite loop +%% instead of crashing due to the existence of list comprehensions that have a +%% normal success typing. These were added to the monitor_diskspace's SCC and +%% dialyzer_typesig didn't try to assign unit() to monitor_diskspace, as it +%% required all the members of the SCC to return none(). +%% +%% Testcase was submitted in erlang-questions mailing list by Prashanth Mundkur +%% (http://erlang.org/pipermail/erlang-questions/2011-May/058063.html) + +-module(no_return_bug). +-export([diskspace/1, monitor_diskspace/2, refresh_tags/1, monitor_launch/0]). + +-type diskinfo() :: {non_neg_integer(), non_neg_integer()}. + +-spec diskspace(nonempty_string()) -> {'ok', diskinfo()} | {'error', term()}. +diskspace(Path) -> + case Path of + "a" -> {ok, {0,0}}; + _ -> {error, error} + end. + +-spec monitor_diskspace(nonempty_string(), + [{diskinfo(), nonempty_string()}]) -> + no_return(). +monitor_diskspace(Root, Vols) -> + Df = fun(VolName) -> + diskspace(filename:join([Root, VolName])) + end, + NewVols = [{Space, VolName} + || {VolName, {ok, Space}} + <- [{VolName, Df(VolName)} + || {_OldSpace, VolName} <- Vols]], + monitor_diskspace(Root, NewVols). + +-spec refresh_tags(nonempty_string()) -> no_return(). +refresh_tags(Root) -> + {ok, _} = diskspace(Root), + refresh_tags(Root). + +monitor_launch() -> + spawn_link(fun() -> refresh_tags("abc") end), + spawn_link(fun() -> monitor_diskspace("root", [{{0,0}, "a"}]) end). diff --git a/lib/dialyzer/test/small_SUITE_data/src/nowarnunused.erl b/lib/dialyzer/test/small_SUITE_data/src/nowarnunused.erl new file mode 100644 index 0000000000..63daeee9e3 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/nowarnunused.erl @@ -0,0 +1,7 @@ +-module(nowarnunused). + +-compile({nowarn_unused_function, return_error/2}). + +-spec return_error(integer(), any()) -> no_return(). +return_error(Line, Message) -> + throw({error, {Line, ?MODULE, Message}}). diff --git a/lib/edoc/Makefile b/lib/edoc/Makefile index e512e390e3..1add669398 100644 --- a/lib/edoc/Makefile +++ b/lib/edoc/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id$ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/edoc/doc/Makefile b/lib/edoc/doc/Makefile index a0f6484382..c5f68b25d0 100644 --- a/lib/edoc/doc/Makefile +++ b/lib/edoc/doc/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id: Makefile,v 1.1.1.1 2004/10/04 13:53:33 richardc Exp $ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/edoc/doc/overview.edoc b/lib/edoc/doc/overview.edoc index bd603b7a13..fa699c6f08 100644 --- a/lib/edoc/doc/overview.edoc +++ b/lib/edoc/doc/overview.edoc @@ -1084,10 +1084,11 @@ Details: the Erlang programming language.</li> <li>`boolean()' is the subset of `atom()' consisting of the atoms `true' and `false'.</li> - <li>`char()' is a subset of - `integer()' representing character codes.</li> + <li>`char()' is the subset of `integer()' representing + Unicode character codes: hex 000000-10FFFF.</li> <li>`tuple()' is the set of all tuples `{...}'.</li> - <li>`list(T)' is just an alias for `[T]'.</li> + <li>`list(T)' is just an alias for `[T]'; list() is an alias + for `list(any())', i.e., `[any()]'.</li> <li>`nil()' is an alias for the empty list `[]'.</li> <li>`cons(H,T)' is the list constructor. This is usually not used directly. It is possible to recursively define `list(T) diff --git a/lib/edoc/doc/src/Makefile b/lib/edoc/doc/src/Makefile index 5ee0096f0f..b933094464 100644 --- a/lib/edoc/doc/src/Makefile +++ b/lib/edoc/doc/src/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id$ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/edoc/include/Makefile b/lib/edoc/include/Makefile index 0533c27567..5b2ad38c9d 100644 --- a/lib/edoc/include/Makefile +++ b/lib/edoc/include/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id$ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/edoc/priv/edoc_generate.src b/lib/edoc/priv/edoc_generate.src index e87fdbc902..7ec89207b0 100644 --- a/lib/edoc/priv/edoc_generate.src +++ b/lib/edoc/priv/edoc_generate.src @@ -14,9 +14,6 @@ # Portions created by Ericsson are Copyright 1999-2000, Ericsson # Utvecklings AB. All Rights Reserved.'' # -# $Id$ -# -# #EDOC_DIR=/clearcase/otp/internal_tools/edoc EDOC_DIR=/home/otp/sgml/edoc-%EDOC_VSN% diff --git a/lib/edoc/src/Makefile b/lib/edoc/src/Makefile index 9c5a9d30d1..fcb0b61292 100644 --- a/lib/edoc/src/Makefile +++ b/lib/edoc/src/Makefile @@ -23,7 +23,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/edoc-$(VSN) EBIN = ../ebin XMERL = ../../xmerl -ERL_COMPILE_FLAGS += -I../include -I$(XMERL)/include +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_deprecated_guard +ERL_COMPILE_FLAGS += -pa $(XMERL) -I../include -I$(XMERL)/include +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_deprecated_guard SOURCES= \ edoc.erl edoc_data.erl edoc_doclet.erl edoc_extract.erl \ diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl index 360f2dbc9e..a279f7dcb3 100644 --- a/lib/edoc/src/edoc.erl +++ b/lib/edoc/src/edoc.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @copyright 2001-2007 Richard Carlsson %% @author Richard Carlsson <[email protected]> %% @version {@version} @@ -60,8 +58,6 @@ -compile({no_auto_import,[error/1]}). --import(edoc_report, [report/2, report/3, error/1, error/3]). - -include("edoc.hrl"). @@ -179,8 +175,8 @@ application(App, Options) when is_atom(App) -> Dir when is_list(Dir) -> application(App, Dir, Options); _ -> - report("cannot find application directory for '~s'.", - [App]), + edoc_report:report("cannot find application directory for '~s'.", + [App]), exit(error) end. @@ -663,8 +659,8 @@ read_source(Name, Opts0) -> check_forms(Forms, Name), Forms; {error, R} -> - error({"error reading file '~s'.", - [edoc_lib:filename(Name)]}), + edoc_report:error({"error reading file '~s'.", + [edoc_lib:filename(Name)]}), exit({error, R}) end. @@ -688,11 +684,10 @@ check_forms(Fs, Name) -> error_marker -> case erl_syntax:error_marker_info(F) of {L, M, D} -> - error(L, Name, {format_error, M, D}); - + edoc_report:error(L, Name, {format_error, M, D}); Other -> - report(Name, "unknown error in " - "source code: ~w.", [Other]) + edoc_report:report(Name, "unknown error in " + "source code: ~w.", [Other]) end, exit(error); _ -> diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl index 27f43dca5a..e3b5a0d51b 100644 --- a/lib/edoc/src/edoc_data.erl +++ b/lib/edoc/src/edoc_data.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @private %% @copyright 2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> diff --git a/lib/edoc/src/edoc_doclet.erl b/lib/edoc/src/edoc_doclet.erl index 30eef3e63a..c66be9d7c7 100644 --- a/lib/edoc/src/edoc_doclet.erl +++ b/lib/edoc/src/edoc_doclet.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @copyright 2003-2006 Richard Carlsson %% @author Richard Carlsson <[email protected]> %% @see edoc @@ -52,7 +50,7 @@ -define(IMAGE, "erlang.png"). -define(NL, "\n"). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). %% Sources is the list of inputs in the order they were found. Packages %% and Modules are sorted lists of atoms without duplicates. (They diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl index 5e28762c53..1209d86fe5 100644 --- a/lib/edoc/src/edoc_extract.erl +++ b/lib/edoc/src/edoc_extract.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id: $ -%% %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> %% @see edoc @@ -238,8 +236,8 @@ file(File, Context, Env, Opts) -> case file:read_file(File) of {ok, Bin} -> {ok, text(binary_to_list(Bin), Context, Env, Opts, File)}; - {error, _R} = Error -> - Error + {error, _} = Error -> + Error end. @@ -298,8 +296,8 @@ get_module_info(Forms, File) -> {Name, Vars} = case lists:keyfind(module, 1, L) of {module, N} when is_atom(N) -> {N, none}; - {module, {N, _Vs} = NVs} when is_atom(N) -> - NVs; + {module, {N, _}=Mod} when is_atom(N) -> + Mod; _ -> report(File, "module name missing.", []), exit(error) diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl index 3ec87b7060..1c0841815f 100644 --- a/lib/edoc/src/edoc_layout.erl +++ b/lib/edoc/src/edoc_layout.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id: $ -%% %% @author Richard Carlsson <[email protected]> %% @copyright 2001-2006 Richard Carlsson %% @see edoc @@ -33,7 +31,7 @@ -import(edoc_report, [report/2]). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). -define(HTML_EXPORT, xmerl_html). -define(DEFAULT_XML_EXPORT, ?HTML_EXPORT). @@ -959,12 +957,16 @@ local_label(R) -> xhtml(Title, CSS, Body) -> [{html, [?NL, - {head, [?NL, - {title, Title}, - ?NL] ++ CSS}, - ?NL, - {body, [{bgcolor, "white"}], Body}, - ?NL] + {head, [?NL, + {meta, [{'http-equiv',"Content-Type"}, + {content, "text/html; charset=ISO-8859-1"}], + []}, + ?NL, + {title, Title}, + ?NL] ++ CSS}, + ?NL, + {body, [{bgcolor, "white"}], Body}, + ?NL] }, ?NL]. diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl index 585e30a2d2..6c698e83ef 100644 --- a/lib/edoc/src/edoc_lib.erl +++ b/lib/edoc/src/edoc_lib.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> %% @see edoc @@ -40,7 +38,7 @@ -import(edoc_report, [report/2, warning/2]). -include("edoc.hrl"). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). -define(FILE_BASE, "/"). @@ -494,7 +492,7 @@ uri_get_file(File0) -> uri_get_http(URI) -> %% Try using option full_result=false case catch {ok, httpc:request(get, {URI,[]}, [], - [{full_result, false}])} of + [{full_result, false}])} of {'EXIT', _} -> uri_get_http_r10(URI); Result -> diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl index 6943f1bdb8..3ce4cde4fb 100644 --- a/lib/edoc/src/edoc_parser.yrl +++ b/lib/edoc/src/edoc_parser.yrl @@ -23,9 +23,6 @@ %% USA %% %% Author contact: [email protected] -%% -%% $Id $ -%% %% ===================================================================== Nonterminals @@ -362,10 +359,10 @@ parse_spec(S, L) -> {ok, Spec} -> Spec; {error, E} -> - throw_error(E, L) + throw_error({parse_spec, E}, L) end; {error, E, _} -> - throw_error(E, L) + throw_error({parse_spec, E}, L) end. %% --------------------------------------------------------------------- diff --git a/lib/edoc/src/edoc_report.erl b/lib/edoc/src/edoc_report.erl index ee54c60c90..f082513bee 100644 --- a/lib/edoc/src/edoc_report.erl +++ b/lib/edoc/src/edoc_report.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @private %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> diff --git a/lib/edoc/src/edoc_run.erl b/lib/edoc/src/edoc_run.erl index 96e5ea4631..1355db840f 100644 --- a/lib/edoc/src/edoc_run.erl +++ b/lib/edoc/src/edoc_run.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @copyright 2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> %% @see edoc diff --git a/lib/edoc/src/edoc_scanner.erl b/lib/edoc/src/edoc_scanner.erl index 9d2e6f3aed..8e895ad1ad 100644 --- a/lib/edoc/src/edoc_scanner.erl +++ b/lib/edoc/src/edoc_scanner.erl @@ -13,8 +13,6 @@ %% AB. Portions created by Ericsson are Copyright 1999, Ericsson %% Utvecklings AB. All Rights Reserved.'' %% -%% $Id: $ -%% %% @private %% @copyright Richard Carlsson 2001-2003. Portions created by Ericsson %% are Copyright 1999, Ericsson Utvecklings AB. All Rights Reserved. diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl index 519ade726f..bfb17515be 100644 --- a/lib/edoc/src/edoc_specs.erl +++ b/lib/edoc/src/edoc_specs.erl @@ -87,8 +87,9 @@ dummy_spec(Form) -> #tag{name = spec, line = element(2, hd(TypeSpecs)), origin = code, data = S}. --spec docs(Forms::[syntaxTree()], CommentFun) -> dict() when - CommentFun :: fun(([syntaxTree()], Line :: term()) -> #tag{}). +-spec docs(Forms::[syntaxTree()], + CommentFun :: fun( ([syntaxTree()], Line :: term()) -> #tag{} )) + -> dict(). %% @doc Find comments after -type/-opaque declarations. %% Postcomments "inside" the type are skipped. @@ -305,8 +306,6 @@ d2e({ann_type,_,[V, T0]}) -> %% layout module. T = d2e(T0), ?add_t_ann(T, element(3, V)); -d2e({type,_,no_return,[]}) -> - #t_type{name = #t_name{name = none}}; d2e({remote_type,_,[{atom,_,M},{atom,_,F},Ts0]}) -> Ts = d2e(Ts0), typevar_anno(#t_type{name = #t_name{module = M, name = F}, args = Ts}, Ts); diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl index 8ee8f87b5f..80989428ce 100644 --- a/lib/edoc/src/edoc_tags.erl +++ b/lib/edoc/src/edoc_tags.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @private %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> diff --git a/lib/edoc/src/edoc_types.erl b/lib/edoc/src/edoc_types.erl index 1ded63dffe..a54544868c 100644 --- a/lib/edoc/src/edoc_types.erl +++ b/lib/edoc/src/edoc_types.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @private %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> @@ -34,13 +32,13 @@ %% @headerfile "edoc_types.hrl" -include("edoc_types.hrl"). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). is_predefined(any, 0) -> true; is_predefined(atom, 0) -> true; is_predefined(binary, 0) -> true; -is_predefined(bool, 0) -> true; +is_predefined(bool, 0) -> true; % kept for backwards compatibility is_predefined(char, 0) -> true; is_predefined(cons, 2) -> true; is_predefined(deep_string, 0) -> true; @@ -51,6 +49,7 @@ is_predefined(list, 0) -> true; is_predefined(list, 1) -> true; is_predefined(nil, 0) -> true; is_predefined(none, 0) -> true; +is_predefined(no_return, 0) -> true; is_predefined(number, 0) -> true; is_predefined(pid, 0) -> true; is_predefined(port, 0) -> true; diff --git a/lib/edoc/src/edoc_wiki.erl b/lib/edoc/src/edoc_wiki.erl index 9a31bc9a82..2f2d14853c 100644 --- a/lib/edoc/src/edoc_wiki.erl +++ b/lib/edoc/src/edoc_wiki.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @private %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <[email protected]> @@ -70,7 +68,7 @@ -export([parse_xml/2, expand_text/2]). -include("edoc.hrl"). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). -define(BASE_HEADING, 3). @@ -82,8 +80,8 @@ parse_xml(Data, Line) -> parse_xml_1(Text, Line) -> Text1 = "<doc>" ++ Text ++ "</doc>", - Options = [{line, Line}, {encoding, "iso-8859-1"}], - case catch {ok, xmerl_scan:string(Text1, Options)} of + Opts = [{line, Line}, {encoding, 'iso-8859-1'}], + case catch {ok, xmerl_scan:string(Text1, Opts)} of {ok, {E, _}} -> E#xmlElement.content; {'EXIT', {fatal, {Reason, L, _C}}} -> @@ -360,10 +358,7 @@ par_text(Cs, As, Bs, E, Es) -> [] -> Bs; _ -> [#xmlElement{name = p, content = Es1} | Bs] end, - Bs1 = case Ss of - [] -> Bs0; - _ -> [#xmlText{value = Ss} | Bs0] - end, + Bs1 = [#xmlText{value = Ss} | Bs0], case Cs2 of [] -> par(Es, [], Bs1); diff --git a/lib/edoc/src/otpsgml_layout.erl b/lib/edoc/src/otpsgml_layout.erl index 45f74b299e..d425dc0ed8 100644 --- a/lib/edoc/src/otpsgml_layout.erl +++ b/lib/edoc/src/otpsgml_layout.erl @@ -14,8 +14,6 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% %% @author Richard Carlsson <[email protected]> %% @author Kenneth Lundin <[email protected]> %% @copyright 2001-2004 Richard Carlsson @@ -34,7 +32,7 @@ -import(edoc_report, [report/2]). --include("xmerl.hrl"). +-include_lib("xmerl/include/xmerl.hrl"). -define(SGML_EXPORT, xmerl_otpsgml). -define(DEFAULT_XML_EXPORT, ?SGML_EXPORT). diff --git a/lib/edoc/test/edoc_SUITE.erl b/lib/edoc/test/edoc_SUITE.erl index 0d57591e3e..5b95c35756 100644 --- a/lib/edoc/test/edoc_SUITE.erl +++ b/lib/edoc/test/edoc_SUITE.erl @@ -13,8 +13,6 @@ %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings %% AB. All Rights Reserved.'' %% -%% $Id$ -%% -module(edoc_SUITE). -include_lib("test_server/include/test_server.hrl"). diff --git a/lib/erl_docgen/priv/bin/xref_mod_app.escript b/lib/erl_docgen/priv/bin/xref_mod_app.escript index 13671ef2f8..c2bd62f9e0 100755 --- a/lib/erl_docgen/priv/bin/xref_mod_app.escript +++ b/lib/erl_docgen/priv/bin/xref_mod_app.escript @@ -73,7 +73,12 @@ usage() -> modapp(TopDir) -> AppDirs = filelib:wildcard(filename:join([TopDir,"lib","*"])), AM = [appmods(D) || D <- AppDirs], - lists:keysort(1, [{M,A} || {A,Ms} <- AM, M <- Ms]). + ERTS = [preloaded(TopDir) || lists:keyfind("erts", 1, AM) =:= false], + lists:keysort(1, [{M,A} || {A,Ms} <- ERTS++AM, M <- Ms]). + +preloaded(TopDir) -> + {"preloaded",Mods} = appmods(filename:join([TopDir,"erts","preloaded"])), + {"erts",Mods}. %% It's OK if too much data is generated as long as all applications %% and all modules are mentioned. diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index 982572aeef..a9052f29e5 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -433,6 +433,8 @@ <!-- Search "local types" as well --> <xsl:variable name="local_types" select="ancestor::desc/preceding-sibling::type + [string-length(@name) > 0] + | ancestor::type_desc/preceding-sibling::type [string-length(@name) > 0]"/> <xsl:variable name="has_anno_in_local_type"> <xsl:for-each select="$local_types"> diff --git a/lib/erl_docgen/priv/xsl/db_man.xsl b/lib/erl_docgen/priv/xsl/db_man.xsl index 25b62f68c5..8db4714249 100644 --- a/lib/erl_docgen/priv/xsl/db_man.xsl +++ b/lib/erl_docgen/priv/xsl/db_man.xsl @@ -363,6 +363,8 @@ <!-- Search "local types" as well --> <xsl:variable name="local_types" select="ancestor::desc/preceding-sibling::type + [string-length(@name) > 0] + | ancestor::type_desc/preceding-sibling::type [string-length(@name) > 0]"/> <xsl:variable name="has_anno_in_local_type"> <xsl:for-each select="$local_types"> diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl index 5119e3e36a..a3d601d861 100644 --- a/lib/erl_docgen/priv/xsl/db_pdf.xsl +++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl @@ -410,6 +410,8 @@ <!-- Search "local types" as well --> <xsl:variable name="local_types" select="ancestor::desc/preceding-sibling::type + [string-length(@name) > 0] + | ancestor::type_desc/preceding-sibling::type [string-length(@name) > 0]"/> <xsl:variable name="has_anno_in_local_type"> <xsl:for-each select="$local_types"> diff --git a/lib/erl_interface/src/connect/ei_resolve.c b/lib/erl_interface/src/connect/ei_resolve.c index 50c5a4161d..ba8f8fbce3 100644 --- a/lib/erl_interface/src/connect/ei_resolve.c +++ b/lib/erl_interface/src/connect/ei_resolve.c @@ -185,7 +185,12 @@ static int verify_dns_configuration(void) * align: increment buf until it is dword-aligned, reduce len by same amount. * advance: increment buf by n bytes, reduce len by same amount . */ -#define align_buf(buf,len) for (;(((unsigned)buf)&0x3); (buf)++, len--) +#if defined SIZEOF_VOID_P +#define ALIGNBYTES (SIZEOF_VOID_P - 1) +#else +#define ALIGNBYTES (sizeof(void*) - 1) +#endif +#define align_buf(buf,len) for (;(((unsigned)buf) & ALIGNBYTES); (buf)++, len--) #define advance_buf(buf,len,n) ((buf)+=(n),(len)-=(n)) /* "and now the tricky part..." */ diff --git a/lib/et/src/et_wx_viewer.erl b/lib/et/src/et_wx_viewer.erl index 7d4286ed9d..386f8fc86b 100644 --- a/lib/et/src/et_wx_viewer.erl +++ b/lib/et/src/et_wx_viewer.erl @@ -257,10 +257,10 @@ parse_opt([H | T], S, CollectorOpt) -> Actors = [create_actor(Name) || Name <- ActorNames2], parse_opt(T, S#state{actors = Actors}, CollectorOpt); {include, ActorNames} when is_list(ActorNames) -> - Actors = [opt_create_actor(Name, include, S#state.actors) || Name <- ActorNames], + Actors = [opt_create_actor(Name, include, S) || Name <- ActorNames], parse_opt(T, S#state{actors = Actors}, CollectorOpt); {exclude, ActorNames} when is_list(ActorNames) -> - Actors = [opt_create_actor(Name, exclude, S#state.actors) || Name <- ActorNames], + Actors = [opt_create_actor(Name, exclude, S) || Name <- ActorNames], parse_opt(T, S#state{actors = Actors}, CollectorOpt); {first_event, _FirstKey} -> %% NYI diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl index dfb08c90b2..25b5cde09c 100644 --- a/lib/eunit/src/eunit_surefire.erl +++ b/lib/eunit/src/eunit_surefire.erl @@ -64,6 +64,7 @@ }). -record(testsuite, { + id = 0 :: integer(), name = <<>> :: binary(), time = 0 :: integer(), output = <<>> :: binary(), @@ -76,7 +77,7 @@ -record(state, {verbose = false, indent = 0, xmldir = ".", - testsuite = #testsuite{} + testsuites = [] :: [#testsuite{}] }). start() -> @@ -89,55 +90,60 @@ init(Options) -> XMLDir = proplists:get_value(dir, Options, ?XMLDIR), St = #state{verbose = proplists:get_bool(verbose, Options), xmldir = XMLDir, - testsuite = #testsuite{}}, + testsuites = []}, receive {start, _Reference} -> St end. terminate({ok, _Data}, St) -> - TestSuite = St#state.testsuite, + TestSuites = St#state.testsuites, XmlDir = St#state.xmldir, - write_report(TestSuite, XmlDir), + write_reports(TestSuites, XmlDir), ok; terminate({error, _Reason}, _St) -> %% Don't report any errors here, since eunit_tty takes care of that. %% Just terminate. ok. -handle_begin(group, Data, St) -> +handle_begin(Kind, Data, St) when Kind == group; Kind == test -> + %% Run this code both for groups and tests; test is a bit + %% surprising: This is a workaround for the fact that we don't get + %% a group (handle_begin(group, ...) for testsuites (modules) + %% which only have one test case. In that case we get a test case + %% with an id comprised of just one integer - the group id. NewId = proplists:get_value(id, Data), case NewId of [] -> St; - [_GroupId] -> + [GroupId] -> Desc = proplists:get_value(desc, Data), - TestSuite = St#state.testsuite, - NewTestSuite = TestSuite#testsuite{name = Desc}, - St#state{testsuite=NewTestSuite}; + TestSuite = #testsuite{id = GroupId, name = Desc}, + St#state{testsuites=store_suite(TestSuite, St#state.testsuites)}; %% Surefire format is not hierarchic: Ignore subgroups: _ -> St - end; -handle_begin(test, _Data, St) -> - St. + end. handle_end(group, Data, St) -> %% Retrieve existing test suite: case proplists:get_value(id, Data) of [] -> St; - [_GroupId|_] -> - TestSuite = St#state.testsuite, + [GroupId|_] -> + TestSuites = St#state.testsuites, + TestSuite = lookup_suite_by_group_id(GroupId, TestSuites), %% Update TestSuite data: Time = proplists:get_value(time, Data), Output = proplists:get_value(output, Data), NewTestSuite = TestSuite#testsuite{ time = Time, output = Output }, - St#state{testsuite=NewTestSuite} + St#state{testsuites=store_suite(NewTestSuite, TestSuites)} end; handle_end(test, Data, St) -> %% Retrieve existing test suite: - TestSuite = St#state.testsuite, + [GroupId|_] = proplists:get_value(id, Data), + TestSuites = St#state.testsuites, + TestSuite = lookup_suite_by_group_id(GroupId, TestSuites), %% Create test case: Name = format_name(proplists:get_value(source, Data), @@ -149,7 +155,7 @@ handle_end(test, Data, St) -> TestCase = #testcase{name = Name, description = Desc, time = Time,output = Output}, NewTestSuite = add_testcase_to_testsuite(Result, TestCase, TestSuite), - St#state{testsuite=NewTestSuite}. + St#state{testsuites=store_suite(NewTestSuite, TestSuites)}. %% Cancel group does not give information on the individual cancelled test case %% We ignore this event @@ -157,7 +163,9 @@ handle_cancel(group, _Data, St) -> St; handle_cancel(test, Data, St) -> %% Retrieve existing test suite: - TestSuite = St#state.testsuite, + [GroupId|_] = proplists:get_value(id, Data), + TestSuites = St#state.testsuites, + TestSuite = lookup_suite_by_group_id(GroupId, TestSuites), %% Create test case: Name = format_name(proplists:get_value(source, Data), @@ -171,7 +179,7 @@ handle_cancel(test, Data, St) -> NewTestSuite = TestSuite#testsuite{ skipped = TestSuite#testsuite.skipped+1, testcases=[TestCase|TestSuite#testsuite.testcases] }, - St#state{testsuite=NewTestSuite}. + St#state{testsuites=store_suite(NewTestSuite, TestSuites)}. format_name({Module, Function, Arity}, Line) -> lists:flatten([atom_to_list(Module), ":", atom_to_list(Function), "/", @@ -183,6 +191,12 @@ format_desc(Desc) when is_binary(Desc) -> format_desc(Desc) when is_list(Desc) -> Desc. +lookup_suite_by_group_id(GroupId, TestSuites) -> + #testsuite{} = lists:keyfind(GroupId, #testsuite.id, TestSuites). + +store_suite(#testsuite{id=GroupId} = TestSuite, TestSuites) -> + lists:keystore(GroupId, #testsuite.id, TestSuites, TestSuite). + %% Add testcase to testsuite depending on the result of the test. add_testcase_to_testsuite(ok, TestCaseTmp, TestSuite) -> TestCase = TestCaseTmp#testcase{ result = ok }, @@ -214,6 +228,10 @@ add_testcase_to_testsuite({error, Exception}, TestCaseTmp, TestSuite) -> %% Write a report to the XML directory. %% This function opens the report file, calls write_report_to/2 and closes the file. %% ---------------------------------------------------------------------------- +write_reports(TestSuites, XmlDir) -> + lists:foreach(fun(TestSuite) -> write_report(TestSuite, XmlDir) end, + TestSuites). + write_report(#testsuite{name = Name} = TestSuite, XmlDir) -> Filename = filename:join(XmlDir, lists:flatten(["TEST-", escape_suitename(Name)], ".xml")), case file:open(Filename, [write, raw]) of diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl index 0b47c7b6e1..f1be658054 100644 --- a/lib/hipe/cerl/erl_bif_types.erl +++ b/lib/hipe/cerl/erl_bif_types.erl @@ -702,7 +702,7 @@ type(erlang, demonitor, 1, Xs) -> type(erlang, demonitor, 2, Xs) -> strict(arg_types(erlang, demonitor, 2), Xs, fun (_) -> t_boolean() end); type(erlang, disconnect_node, 1, Xs) -> - strict(arg_types(erlang, disconnect_node, 1), Xs, fun (_) -> t_boolean() end); + strict(arg_types(erlang, disconnect_node, 1), Xs, fun (_) -> t_sup([t_boolean(), t_atom('ignored')]) end); type(erlang, display, 1, _) -> t_atom('true'); type(erlang, display_string, 1, Xs) -> strict(arg_types(erlang, display_string, 1), Xs, fun(_) -> t_atom('true') end); @@ -1124,7 +1124,7 @@ type(erlang, nodes, 0, _) -> t_list(t_node()); type(erlang, nodes, 1, Xs) -> strict(arg_types(erlang, nodes, 1), Xs, fun (_) -> t_list(t_node()) end); type(erlang, now, 0, _) -> - t_time(); + t_timestamp(); type(erlang, open_port, 2, Xs) -> strict(arg_types(erlang, open_port, 2), Xs, fun (_) -> t_port() end); type(erlang, phash, 2, Xs) -> @@ -1585,8 +1585,7 @@ type(erlang, system_info, 1, Xs) -> ['multi_scheduling_blockers'] -> t_list(t_pid()); ['os_type'] -> - t_tuple([t_sup([t_atom('ose'), % XXX: undocumented - t_atom('unix'), + t_tuple([t_sup([t_atom('unix'), t_atom('vxworks'), t_atom('win32')]), t_atom()]); @@ -2693,7 +2692,7 @@ type(os, getpid, 0, _) -> t_string(); type(os, putenv, 2, Xs) -> strict(arg_types(os, putenv, 2), Xs, fun (_) -> t_atom('true') end); type(os, timestamp, 0, _) -> - t_time(); + t_timestamp(); %%-- re ----------------------------------------------------------------------- type(re, compile, 1, Xs) -> strict(arg_types(re, compile, 1), Xs, @@ -4458,6 +4457,9 @@ t_date() -> t_time() -> t_tuple([t_non_neg_fixnum(), t_non_neg_fixnum(), t_non_neg_fixnum()]). +t_timestamp() -> + t_tuple([t_non_neg_fixnum(), t_non_neg_fixnum(), t_non_neg_fixnum()]). + t_packet() -> t_sup([t_binary(), t_iolist(), t_httppacket()]). diff --git a/lib/ic/doc/src/notes.xml b/lib/ic/doc/src/notes.xml index 5f6c31069c..de519d5f84 100644 --- a/lib/ic/doc/src/notes.xml +++ b/lib/ic/doc/src/notes.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1998</year><year>2010</year> + <year>1998</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -31,6 +31,22 @@ </header> <section> + <title>IC 4.2.27</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p> + Reduced compile overhead (Thanks to Haitao Li).</p> + <p> + Own Id: OTP-9460 </p> + </item> + </list> + </section> + </section> + + <section> <title>IC 4.2.26</title> <section> diff --git a/lib/ic/src/ic_pp.erl b/lib/ic/src/ic_pp.erl index db06118d32..8b53473caa 100644 --- a/lib/ic/src/ic_pp.erl +++ b/lib/ic/src/ic_pp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. 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 @@ -92,6 +92,14 @@ %% %%====================================================================================== +%% Multiple Include Optimization +%% +%% Algorithm described at: +%% http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html +-record(mio, {valid = true, %% multiple include valid + cmacro, %% controlling macro of the current conditional directive + depth = 0, %% conditional directive depth + included = []}). @@ -130,7 +138,7 @@ run(FileList, FileName, IncDir, Flags) -> %%---------------------------------------------------------- %% Run the second phase, i.e expand macros %%---------------------------------------------------------- - {Out, Err, War, _Defs, IfCou} = expand(File, FileName, IncDir, Flags), + {Out, Err, War, _Defs, _Mio, IfCou} = expand(File, FileName, IncDir, Flags), %%---------------------------------------------------------- %% Check if all #if #ifdef #ifndef have a matching #endif @@ -155,9 +163,9 @@ run(FileList, FileName, IncDir, Flags) -> %% The entry for all included files %% %% -%% Output {Out, Defs, Err, War} +%% Output {Out, Err, War, Defs, MultipleIncludeValid} %%====================================================================================== -run_include(FileName, FileList, _Out, Defs, Err, War, IncLine, IncFile, IncDir) -> +run_include(FileName, FileList, _Out, Defs, Err, War, IncLine, IncFile, IncDir, Mio) -> %%---------------------------------------------------------- %% Run the first phase, i.e tokenise the file @@ -169,18 +177,21 @@ run_include(FileName, FileList, _Out, Defs, Err, War, IncLine, IncFile, IncDir) %%---------------------------------------------------------- %% Run the second phase, i.e expand macros %%---------------------------------------------------------- - - %% Try first pass without file info start/end - {OutT, ErrT, WarT, DefsT, IfCouT} = - expand(File, Defs, Err, War, [FileName|IncFile], IncDir), - - {Out2, Err2, War2, Defs2, IfCou2} = - case only_nls(OutT) of - true -> %% The file is defined before - {["\n"], ErrT, WarT, DefsT, IfCouT}; - false -> %% The file is not defined before, try second pass - expand([FileInfoStart|File]++FileInfoEnd, Defs, Err, War, [FileName|IncFile], IncDir) - end, + {Out2, Err2, War2, Defs2, Mio2, IfCou2} = + expand([FileInfoStart|File]++FileInfoEnd, Defs, Err, War, + [FileName|IncFile], IncDir, + #mio{included=Mio#mio.included}), + + MergeIncluded = sets:to_list(sets:from_list(Mio#mio.included ++ Mio2#mio.included)), + + Mio3 = + case {Mio2#mio.valid, Mio2#mio.cmacro} of + {V, Macro} when V == false; + Macro == undefined -> + update_mio(Mio#mio{included=MergeIncluded}); + {true, _} -> + update_mio({include, FileName}, Mio#mio{included=MergeIncluded}) + end, %%---------------------------------------------------------- %% Check if all #if #ifdef #ifndef have a matching #endif @@ -192,26 +203,7 @@ run_include(FileName, FileList, _Out, Defs, Err, War, IncLine, IncFile, IncDir) [] end, - {Out2, Defs2, Err2++IfError, War2}. - - - -%% Return true if there is no data -%% other than new lines -only_nls([]) -> - true; -only_nls(["\n"|Rem]) -> - only_nls(Rem); -only_nls(["\r","\n"|Rem]) -> - only_nls(Rem); -only_nls([_|_Rem]) -> - false. - - - - - - + {Out2, Defs2, Err2++IfError, War2, Mio3}. @@ -647,87 +639,86 @@ expand(List, FileName, IncDir, Flags) -> %% Get all definitions from preprocessor commnads %% and merge them on top of the file collected. CLDefs = get_cmd_line_defs(Flags), - expand(List, [], [], CLDefs, [FileName], IncDir, check_all, [], [], 1, FileName). - -expand(List, Defs, Err, War, [FileName|IncFile], IncDir) -> - expand(List, [], [], Defs, [FileName|IncFile], IncDir, check_all, Err, War, 1, FileName). + expand(List, [], [], CLDefs, [FileName], IncDir, #mio{}, check_all, [], [], 1, FileName). +expand(List, Defs, Err, War, [FileName|IncFile], IncDir, Mio) -> + expand(List, [], [], Defs, [FileName|IncFile], IncDir, Mio, check_all, Err, War, 1, FileName). %%======================================================= %% Main loop for the expansion %%======================================================= -expand([], Out, _SelfRef, Defs, _IncFile, _IncDir, IfCou, Err, War, _L, _FN) -> +expand([], Out, _SelfRef, Defs, _IncFile, _IncDir, Mio, IfCou, Err, War, _L, _FN) -> % io:format("~n ===============~n"), % io:format(" definitions ~p~n",[lists:reverse(Defs)]), % io:format(" found warnings ~p~n",[lists:reverse(War)]), % io:format(" found errors ~p~n",[lists:reverse(Err)]), % io:format(" ===============~n~n~n"), - {Out, Err, War, Defs, IfCou}; + {Out, Err, War, Defs, Mio, IfCou}; -expand([{file_info, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, Str++Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{file_info, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, Str++Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); %%--------------------------------------- %% Searching for endif, %% i.e skip all source lines until matching %% end if is encountered %%--------------------------------------- -expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) +expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "ifdef" -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif+1, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err, War, L, FN); + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); -expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) +expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "ifndef" -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif+1, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err, War, L, FN); + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); -expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) +expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "if" -> - case pp_command(Command, Rem, Defs, IncDir, Err, War, L, FN) of + case pp_command(Command, Rem, Defs, IncDir, Mio, Err, War, L, FN) of {{'if', true}, Rem2, Err2, War2, Nl} -> IfCou2 = {endif, Endif+1, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err2, War2, L+Nl, FN); + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); %% {{'if', false}, Rem2, Err2, War2, Nl} -> Not implemented yet {{'if', error}, Rem2, Err2, War2, Nl} -> IfCou2 = {endif, Endif, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err2, War2, L+Nl, FN) + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN) end; -expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) +expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "endif" -> {_Removed, Rem2, Nl} = read_to_nl(Rem), case Endif of 1 -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L+Nl, FN); _ -> IfCou2 = {endif, Endif-1, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err, War, L+Nl, FN) + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L+Nl, FN) end; -expand([{command,_Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) -> +expand([{command,_Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif, IfLine}, - expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, IfCou2, Err, War, L, FN); + expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); %% Solves a bug when spaces in front of hashmark ! -expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) -> - expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN); +expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> + expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); -expand([{nl,_Nl} | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) -> - expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN); +expand([{nl,_Nl} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> + expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); -expand([_X | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN) -> +expand([_X | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> {_Removed, Rem2, Nl} = read_to_nl(Rem), - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, Err, War, L, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); @@ -736,121 +727,132 @@ expand([_X | Rem], Out, SelfRef, Defs, IncFile, IncDir, {endif, Endif, IfLine}, %%--------------------------------------- %% Check all tokens %%--------------------------------------- -expand([{nl, _N} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [$\n | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L+1, FN); +expand([{nl, _N} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [$\n | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L+1, FN); -expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); -expand([space_exp | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([space_exp | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); -expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L, FN) -> - case pp_command(Command, Rem, Defs, IncDir, Err, War, L, FN) of +expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L, FN) -> + case pp_command(Command, Rem, Defs, IncDir, Mio, Err, War, L, FN) of {define, Rem2, Defs2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); {undef, Rem2, Defs2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); {{include, ok}, FileName, FileCont, Rem2, Nl, Err2, War2} -> - {Out3, Defs3, Err3, War3} = - run_include(FileName, FileCont, Out, Defs, Err2, War2, L+Nl, IncFile, IncDir), + {Out3, Defs3, Err3, War3, Mio2} = + run_include(FileName, FileCont, Out, Defs, Err2, War2, L+Nl, IncFile, IncDir, Mio), Nls = [], Out4 = Out3++Nls++Out, - expand(Rem2, Out4, SelfRef, Defs3, IncFile, IncDir, check_all, Err3, War3, L+Nl, FN); + expand(Rem2, Out4, SelfRef, Defs3, IncFile, IncDir, Mio2, check_all, Err3, War3, L+Nl, FN); {{include, error}, Rem2, Nl, Err2, War2} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); + + {{include, skip}, Rem2} -> + Out2 = [$\n|Out], + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+1, FN); {{ifdef, true}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], + Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, IfCou2, Err2, War2, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); {{ifdef, false}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + Mio2 = update_mio(ifdef, Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {{ifndef, true}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], + Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, IfCou2, Err2, War2, L+Nl, FN); - {{ifndef, false}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); + {{ifndef, false}, Macro, Rem2, Err2, War2, Nl} -> + Out2 = lists:duplicate(Nl,$\n) ++ Out, + Mio2 = update_mio({ifndef, Macro}, Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {endif, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + Mio2 = update_mio(endif, Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {{'if', true}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], + Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, IfCou2, Err2, War2, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); %% {{'if', false}, Removed, Rem2, Nl} -> Not implemented at present {{'if', error}, Rem2, Err2, War2, Nl} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err2, War2, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + Mio2 = update_mio('if', Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {'else', {_Removed, Rem2, Nl}} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], + Out2 = lists:duplicate(Nl,$\n) ++ Out, Err2 = {FN, L, "`else' command is not implemented at present"}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, [Err2|Err], War, L+Nl, FN); + Mio2 = update_mio('else', Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN); {'elif', {_Removed, Rem2, Nl}} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], + Out2 = lists:duplicate(Nl,$\n) ++ Out, Err2 = {FN, L, "`elif' command is not implemented at present"}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, [Err2|Err], War, L+Nl, FN); + Mio2 = update_mio('elif', Mio), + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN); {warning, {WarningText, Rem2, Nl}} -> [FileName|_More] = IncFile, War2 = {FileName, L, "warning: #warning "++detokenise(WarningText)}, - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, [War2|War], L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, [War2|War], L+Nl, FN); {error, {ErrorText, Rem2, Nl}} -> [FileName|_More] = IncFile, Err2 = {FileName, L, detokenise(ErrorText)}, - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, [Err2|Err], War, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, [Err2|Err], War, L+Nl, FN); {{line, ok}, {_Removed, Rem2, Nl}, L2, FN2, LineText} -> Out2 = lists:duplicate(Nl,$\n)++LineText++Out, [_X|IF] = IncFile, IncFile2 = [FN2|IF], - expand(Rem2, Out2, SelfRef, Defs, IncFile2, IncDir, check_all, Err, War, L2, FN2); + expand(Rem2, Out2, SelfRef, Defs, IncFile2, IncDir, update_mio(Mio), check_all, Err, War, L2, FN2); {{line, error}, {_Removed, Rem2, Nl}, Err2} -> - Out2 = [lists:duplicate(Nl,$\n)|Out], - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, [Err2|Err], War, L+Nl, FN); + Out2 = lists:duplicate(Nl,$\n) ++ Out, + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, [Err2|Err], War, L+Nl, FN); hash_mark -> - expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L, FN); + expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L, FN); {pragma, Rem2, Nl, Text} -> Out2 = lists:duplicate(Nl,$\n)++Text++Out, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+Nl, FN); {ident, Rem2, Nl, Text} -> Out2 = lists:duplicate(Nl,$\n)++Text++Out, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+Nl, FN); {not_recognised, {Removed, Rem2, Nl}} -> Text = lists:reverse([$#|Command]), RemovedS = lists:reverse([?space|detokenise(Removed)]), Out2 = [$\n|RemovedS]++Text++Out, + Mio2 = update_mio(Mio), case Command of [X|_T] when ?is_upper(X) -> - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); [X|_T] when ?is_lower(X) -> - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); [X|_T] when ?is_underline(X) -> - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, Err, War, L+Nl, FN); + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); _ -> Err2 = {FN, L, "invalid preprocessing directive name"}, - expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, check_all, [Err2|Err], War, L+Nl, FN) + expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN) end; Else -> @@ -859,19 +861,19 @@ expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, check_all end; -expand([{var, "__LINE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, "__LINE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> LL = io_lib:format("~p",[L]), - expand(Rem, [LL | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, [LL | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, "__FILE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [$",FN,$" | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{var, "__FILE__"}|Rem], Out, SelfRef, Defs, IncFile, Mio, IncDir, IfCou, Err, War, L, FN) -> + expand(Rem, [$",FN,$" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, "__DATE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, "__DATE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {{Y,M,D},{_H,_Mi,_S}} = calendar:universal_time(), Date = io_lib:format("\"~s ~p ~p\"",[month(M),D,Y]), - expand(Rem, [Date | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, [Date | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, "__TIME__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, "__TIME__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {{_Y,_M,_D},{H,Mi,S}} = calendar:universal_time(), HS = if H < 10 -> "0"++integer_to_list(H); true -> integer_to_list(H) @@ -883,40 +885,40 @@ expand([{var, "__TIME__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, true -> integer_to_list(S) end, Time = io_lib:format("\"~s:~s:~s\"",[HS,MiS,SS]), - expand(Rem, [Time | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, [Time | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, "__INCLUDE_LEVEL__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, "__INCLUDE_LEVEL__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> IL = io_lib:format("~p",[length(IncFile)-1]), - expand(Rem, [IL | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, [IL | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, "__BASE_FILE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, "__BASE_FILE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> [BF|_T] = lists:reverse(IncFile), - expand(Rem, [$",BF,$" | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, [$",BF,$" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{var, Var} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{var, Var} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {Out2, Err2, War2, Rem2, SelfRef2} = source_line(Var, Rem, SelfRef, Defs, Err, War, L, FN), - expand(Rem2, [Out2 | Out], SelfRef2, Defs, IncFile, IncDir, IfCou, Err2, War2, L, FN); + expand(Rem2, [Out2 | Out], SelfRef2, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err2, War2, L, FN); -expand([{char, Char} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [Char | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{char, Char} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [Char | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{number, Number} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [Number | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{number, Number} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [Number | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{expanded, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [Str | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{expanded, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [Str | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{self_ref, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{self_ref, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> SelfRef2 = lists:delete(Str,SelfRef), - expand(Rem, Out, SelfRef2, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); + expand(Rem, Out, SelfRef2, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{string, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> - expand(Rem, [$", Str, $" | Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN); +expand([{string, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> + expand(Rem, [$", Str, $" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); -expand([{string_part, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L, FN) -> +expand([{string_part, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {Str2, Rem2, Nl} = expand_string_part([$"|Str], Rem), - expand(Rem2, [Str2| Out], SelfRef, Defs, IncFile, IncDir, IfCou, Err, War, L+Nl, FN). + expand(Rem2, [Str2| Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L+Nl, FN). @@ -954,13 +956,14 @@ expand_string_part([{string_part, Str_part} | Rem], Str, Nl) -> get_cmd_line_defs(Flags) -> Adjusted = parse_cmd_line(Flags,[]), - {_Out, _Err, _War, Defs, _IfCou} = + {_Out, _Err, _War, Defs, _IfCou, _Mio} = expand(tokenise(Adjusted,""), [], [], [], [], [], + #mio{}, check_all, [], [], @@ -1030,10 +1033,10 @@ collect_undefine([C|Rest],Found) -> %%====================================================================================== %%====================================================================================== -pp_command(Command, [space|File], Defs, IncDir, Err, War, L, FN) -> - pp_command(Command, File, Defs, IncDir, Err, War, L, FN); +pp_command(Command, [space|File], Defs, IncDir, Mio, Err, War, L, FN) -> + pp_command(Command, File, Defs, IncDir, Mio, Err, War, L, FN); -pp_command(Command, File, Defs, IncDir, Err, War, L, FN) -> +pp_command(Command, File, Defs, IncDir, Mio, Err, War, L, FN) -> case Command of %%---------------------------------------- @@ -1081,14 +1084,16 @@ pp_command(Command, File, Defs, IncDir, Err, War, L, FN) -> %% #include %%---------------------------------------- "include" -> - case include(File, IncDir) of - {error, Rem, Nl, Err2} -> - {{include, error}, Rem, Nl, [{FN, L, Err2}|Err], War}; - {error, Rem, Nl, Err2, NameNl} -> - {{include, error}, Rem, Nl, [{FN, L+ NameNl, Err2}|Err], War}; - {ok, FileName, FileCont, Rem, Nl} -> - {{include, ok}, FileName, FileCont, Rem, Nl, Err, War} - end; + case include(File, IncDir, Mio) of + {error, Rem, Nl, Err2} -> + {{include, error}, Rem, Nl, [{FN, L, Err2}|Err], War}; + {error, Rem, Nl, Err2, NameNl} -> + {{include, error}, Rem, Nl, [{FN, L+ NameNl, Err2}|Err], War}; + {ok, FileNamePath, FileCont, Rem, Nl} -> + {{include, ok}, FileNamePath, FileCont, Rem, Nl, Err, War}; + {skip, Rem} -> + {{include, skip}, Rem} + end; %%---------------------------------------- %% #ifdef @@ -1127,14 +1132,14 @@ pp_command(Command, File, Defs, IncDir, Err, War, L, FN) -> yes -> {{ifndef, true}, Rem, Err2, War2, Nl}; no -> - {{ifndef, false}, Rem, Err2, War2, Nl} + {{ifndef, false}, Name, Rem, Err2, War2, Nl} end; {ok, Rem, Name, No_of_para, _Parameters, _Macro, Err2, War2, Nl} -> case is_defined_before(Name, No_of_para, Defs) of yes -> {{ifndef, true}, Rem, Err2, War2, Nl}; no -> - {{ifndef, false}, Rem, Err2, War2, Nl} + {{ifndef, false}, Name, Rem, Err2, War2, Nl} end end; @@ -1408,29 +1413,32 @@ undef(_Rem) -> %%=============================================================== %%=============================================================== -include(File, IncDir) -> +include(File, IncDir, Mio) -> case include2(File) of - {ok, FileName, Rem, Nl, FileType} -> - %% The error handling is lite strange just to make it compatible to gcc - case {read_inc_file(FileName, IncDir), Nl, FileType} of - {{ok, FileList, FileNamePath}, _, _} -> - {ok, FileNamePath, FileList, Rem, Nl}; - {{error, Text}, _, own_file} -> - NameNl = count_nl(FileName,0), - Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), - {error, Rem, Nl, Error, NameNl}; - {{error, Text}, 1, sys_file} -> - NameNl = count_nl(FileName,0), - Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), - {error, Rem, Nl, Error, NameNl}; - {{error, _Text}, _, sys_file} -> - {error, Rem, Nl, "`#include' expects \"FILENAME\" or <FILENAME>"} - end; - - {error, {_Removed, Rem, Nl}} -> - {error, Rem, Nl, "`#include' expects \"FILENAME\" or <FILENAME>"} + {ok, FileName, Rem, Nl, FileType} -> + Result = read_inc_file(FileName, IncDir, Mio), + case {Result, Nl, FileType} of + {{ok, FileNamePath, FileCont}, _, _} -> + {ok, FileNamePath, FileCont, Rem, Nl}; + {skip, _, _} -> + {skip, Rem}; + {{error, Text}, _, own_file} -> + NameNl = count_nl(FileName,0), + Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), + {error, Rem, Nl, Error, NameNl}; + {{error, Text}, 1, sys_file} -> + NameNl = count_nl(FileName,0), + Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), + {error, Rem, Nl, Error, NameNl}; + {{error, _Text}, _, sys_file} -> + {error, Rem, Nl, "`#include' expects \"FILENAME\" or <FILENAME>"} + end; + {error, {_Removed, Rem, Nl}} -> + {error, Rem, Nl, "`#include' expects \#FILENAME\" or <FILENAME>"} end. + + count_nl([],Nl) -> Nl; count_nl([$\n|T],Nl) -> @@ -1909,18 +1917,37 @@ include_dir(Flags,IncDir) -> %% Read a included file. Try current dir first then the IncDir list %%=============================================================== -read_inc_file(FileName, IncDir) -> - case catch file:read_file(FileName) of - {ok, Bin} -> - FileList = binary_to_list(Bin), - {ok, FileList, FileName}; +read_inc_file(FileName, IncDir, Mio) -> + case find_inc_file(FileName, IncDir) of + {ok, AbsFile} -> + %% is included before? + case lists:member(FileName, Mio#mio.included) of + false -> + case catch file:read_file(AbsFile) of + {ok, Bin} -> + FileList = binary_to_list(Bin), + {ok, AbsFile, FileList}; + {error, Text} -> + {error, Text} + end; + true -> + skip + end; + {error, Text} -> + {error, Text} + end. + +find_inc_file(FileName, IncDir) -> + case catch file:read_file_info(FileName) of + {ok, _} -> + {ok, FileName}; {error, _} -> - read_inc_file2(FileName, IncDir) + find_inc_file2(FileName, IncDir) end. -read_inc_file2(_FileName, []) -> +find_inc_file2(_FileName, []) -> {error, "No such file or directory"}; -read_inc_file2(FileName, [D|Rem]) -> +find_inc_file2(FileName, [D|Rem]) -> Dir = case lists:last(D) of $/ -> D; @@ -1928,17 +1955,14 @@ read_inc_file2(FileName, [D|Rem]) -> D++"/" end, - case catch file:read_file(Dir++FileName) of - {ok, Bin} -> - FileList = binary_to_list(Bin), - {ok, FileList, Dir++FileName}; + case catch file:read_file_info(Dir++FileName) of + {ok, _} -> + {ok, Dir++FileName}; {error, _} -> - read_inc_file2(FileName, Rem) + find_inc_file2(FileName, Rem) end. - - %%=============================================================== %% Read parameters of a macro or a variable in a source line %%=============================================================== @@ -2135,5 +2159,73 @@ month(11) -> "Nov"; month(12) -> "Dec". +%% Multiple Include Optimization +%% +%% Algorithm described at: +%% http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html +update_mio({include, FileName}, #mio{included=Inc}=Mio) -> + Mio#mio{valid=false, included=[FileName|Inc]}; + +%% valid=false & cmacro=undefined indicates it is already decided this file is +%% not subject to MIO +update_mio(_, #mio{valid=false, depth=0, cmacro=undefined}=Mio) -> + Mio; + +%% if valid=true, there is no non-whitespace tokens before this ifndef +update_mio({'ifndef', Macro}, #mio{valid=true, depth=0, cmacro=undefined}=Mio) -> + Mio#mio{valid=false, cmacro=Macro, depth=1}; + +%% detect any tokens before top level #ifndef +update_mio(_, #mio{valid=true, depth=0, cmacro=undefined}=Mio) -> + Mio#mio{valid=false}; + +%% If cmacro is alreay set, this is after the top level #endif +update_mio({'ifndef', _}, #mio{valid=true, depth=0}=Mio) -> + Mio#mio{valid=false, cmacro=undefined}; + +%% non-top level conditional, just update depth +update_mio({'ifndef', _}, #mio{depth=D}=Mio) when D > 0 -> + Mio#mio{depth=D+1}; +update_mio('ifdef', #mio{depth=D}=Mio) -> + Mio#mio{depth=D+1}; +update_mio('if', #mio{depth=D}=Mio) -> + Mio#mio{depth=D+1}; + +%% top level #else #elif invalidates multiple include optimization +update_mio('else', #mio{depth=1}=Mio) -> + Mio#mio{valid=false, cmacro=undefined}; +update_mio('else', Mio) -> + Mio; +update_mio('elif', #mio{depth=1}=Mio) -> + Mio#mio{valid=false, cmacro=undefined}; +update_mio('elif', Mio) -> + Mio; + +%% AT exit to top level, if the controlling macro is not set, this could be the +%% end of a non-ifndef conditional block, or there were tokens before entering +%% the #ifndef block. In either way, this invalidates the MIO +%% +%% It doesn't matter if `valid` is true at the time of exiting, it is set to +%% true. This will be used to detect if more tokens are following the top +%% level #endif. +update_mio('endif', #mio{depth=1, cmacro=undefined}=Mio) -> + Mio#mio{valid=false, depth=0}; +update_mio('endif', #mio{depth=1}=Mio) -> + Mio#mio{valid=true, depth=0}; +update_mio('endif', #mio{depth=D}=Mio) when D > 1 -> + Mio#mio{valid=true, depth=D-1}; + +%%if more tokens are following the top level #endif. +update_mio('endif', #mio{depth=1, cmacro=undefined}=Mio) -> + Mio#mio{valid=false, depth=0}; +update_mio('endif', #mio{depth=D}=Mio) when D > 0 -> + Mio#mio{valid=true, depth=D-1}; +update_mio(_, Mio) -> + Mio#mio{valid=false}. + +%% clear `valid`, this doesn't matter since #endif will restore it if +%% appropriate +update_mio(Mio) -> + Mio#mio{valid=false}. diff --git a/lib/ic/src/ic_pragma.erl b/lib/ic/src/ic_pragma.erl index 45cb64c9c8..7f2216b9dc 100644 --- a/lib/ic/src/ic_pragma.erl +++ b/lib/ic/src/ic_pragma.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. 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 @@ -1600,9 +1600,8 @@ remove_inheriters(S,RS,InheriterList) -> [_OneOnly] -> ReducedInhList; _Other -> - EtsList = ets:tab2list(S), CleanList = - [X || X <- EtsList, element(1,X) == inherits], + ets:match(S, {inherits,'_','_'}), % CodeOptList = % [X || X <- EtsList, element(1,X) == codeopt], NoInheriters =remove_inheriters2(S,ReducedInhList,CleanList), @@ -1648,9 +1647,8 @@ remove_inh([X],[Y],List,EtsList) -> %%% from others in the list %%%---------------------------------------------- remove_inherited(S,InheriterList) -> - EtsList = ets:tab2list(S), CleanList = - [X || X <- EtsList, element(1,X) == inherits], + ets:match(S, {inherits, '_', '_'}), remove_inherited(S,InheriterList,CleanList). @@ -1694,11 +1692,8 @@ remove_inhed([X],[Y],List,EtsList) -> %% are inherited from scope in the list %%%---------------------------------------------- get_inherited(S,Scope,OpScopeList) -> - EtsList = ets:tab2list(S), - [[element(3,X)] || X <- EtsList, - element(1,X) == inherits, - element(2,X) == Scope, - member([element(3,X)],OpScopeList)]. + EtsList1 = ets:match(S, {inherits, Scope, '$1'}), + [X || X <- EtsList1, member(X, OpScopeList)]. @@ -1771,9 +1766,7 @@ inherits2(_X,Y,Z,EtsList) -> %% false otherwise %% is_inherited_by(Interface1,Interface2,PragmaTab) -> - FullList = ets:tab2list(PragmaTab), - InheritsList = - [X || X <- FullList, element(1,X) == inherits], + InheritsList = ets:match(PragmaTab, {inherits, '_', '_'}), inherits(Interface2,Interface1,InheritsList). diff --git a/lib/ic/vsn.mk b/lib/ic/vsn.mk index 6d6c7fa625..6561ccd2a7 100644 --- a/lib/ic/vsn.mk +++ b/lib/ic/vsn.mk @@ -1 +1 @@ -IC_VSN = 4.2.26 +IC_VSN = 4.2.27 diff --git a/lib/inets/Makefile b/lib/inets/Makefile index ec05efa461..f4c2746b0a 100644 --- a/lib/inets/Makefile +++ b/lib/inets/Makefile @@ -36,6 +36,8 @@ SPECIAL_TARGETS = # ---------------------------------------------------- include $(ERL_TOP)/make/otp_subdir.mk +.PHONY: info gclean + info: @echo "OS: $(OS)" @echo "DOCB: $(DOCB)" @@ -44,3 +46,5 @@ info: @echo "APP_VSN: $(APP_VSN)" @echo "" +gclean: + git clean -fXd diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index f6b6827e93..d1671ac9bd 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -144,7 +144,7 @@ filename() = string() <v>Result = {status_line(), headers(), Body} | {status_code(), Body} | request_id() </v> <v>Body = string() | binary()</v> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> <v>Reason = term() </v> </type> <desc> @@ -194,16 +194,16 @@ filename() = string() <v>Result = {status_line(), headers(), Body} | {status_code(), Body} | request_id() </v> <v>Body = string() | binary()</v> - <v>Profile = profile() </v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> <v>Reason = {connect_failed, term()} | {send_failed, term()} | term() </v> </type> <desc> <p>Sends a HTTP-request. The function can be both synchronous - and asynchronous. In the later case the function will return - <c>{ok, RequestId}</c> and later on the information will be delivered - to the <c>receiver</c> depending on that value. </p> + and asynchronous. In the later case the function will return + <c>{ok, RequestId}</c> and later on the information will be delivered + to the <c>receiver</c> depending on that value. </p> <p>Http option (<c>http_option()</c>) details: </p> <taglist> @@ -211,7 +211,7 @@ filename() = string() <item> <p>Timeout time for the request. </p> <p>The clock starts ticking as soon as the request has been - sent. </p> + sent. </p> <p>Time is in milliseconds. </p> <p>Defaults to <c>infinity</c>. </p> </item> @@ -219,7 +219,7 @@ filename() = string() <tag><c><![CDATA[connect_timeout]]></c></tag> <item> <p>Connection timeout time, used during the initial request, - when the client is <em>connecting</em> to the server. </p> + when the client is <em>connecting</em> to the server. </p> <p>Time is in milliseconds. </p> <p>Defaults to the value of the <c>timeout</c> option. </p> </item> @@ -227,60 +227,61 @@ filename() = string() <tag><c><![CDATA[ssl]]></c></tag> <item> <p>This is the default ssl config option, currently defaults to - <c>essl</c>, see below. </p> + <c>essl</c>, see below. </p> <p>Defaults to <c>[]</c>. </p> </item> <tag><c><![CDATA[ossl]]></c></tag> <item> <p>If using the OpenSSL based (old) implementation of SSL, - these SSL-specific options are used. </p> + these SSL-specific options are used. </p> <p>Defaults to <c>[]</c>. </p> </item> <tag><c><![CDATA[essl]]></c></tag> <item> <p>If using the Erlang based (new) implementation of SSL, - these SSL-specific options are used. </p> + these SSL-specific options are used. </p> <p>Defaults to <c>[]</c>. </p> </item> <tag><c><![CDATA[autoredirect]]></c></tag> <item> - <p>Should the client automatically retrieve the information - from the new URI and return that as the result instead - of a 30X-result code. </p> - <p>Note that for some 30X-result codes automatic redirect - is not allowed. In these cases the 30X-result will always - be returned. </p> - <p>Defaults to <c>true</c>. </p> + <p>Should the client automatically retrieve the information + from the new URI and return that as the result instead + of a 30X-result code. </p> + <p>Note that for some 30X-result codes automatic redirect + is not allowed. In these cases the 30X-result will always + be returned. </p> + <p>Defaults to <c>true</c>. </p> </item> <tag><c><![CDATA[proxy_auth]]></c></tag> <item> <p>A proxy-authorization header using the provided user name and - password will be added to the request. </p> + password will be added to the request. </p> </item> <tag><c><![CDATA[version]]></c></tag> <item> <p>Can be used to make the client act as an <c>HTTP/1.0</c> or - <c>HTTP/0.9</c> client. By default this is an <c>HTTP/1.1</c> - client. When using <c>HTTP/1.0</c> persistent connections will - not be used. </p> - <p>Defaults to the string <c>"HTTP/1.1"</c>. </p> + <c>HTTP/0.9</c> client. By default this is an <c>HTTP/1.1</c> + client. When using <c>HTTP/1.0</c> persistent connections will + not be used. </p> + <p>Defaults to the string <c>"HTTP/1.1"</c>. </p> </item> <tag><c><![CDATA[relaxed]]></c></tag> <item> - <p>If set to <c>true</c> workarounds for known server deviations from - the HTTP-standard are enabled. </p> + <p>If set to <c>true</c> workarounds for known server deviations + from the HTTP-standard are enabled. </p> <p>Defaults to <c>false</c>. </p> </item> <tag><c><![CDATA[url_encode]]></c></tag> <item> - <p>Will apply Percent-encoding, also known as URL encoding on the URL.</p> + <p>Will apply Percent-encoding, also known as URL encoding on the + URL.</p> <p>Defaults to <c>false</c>. </p> </item> </taglist> @@ -296,77 +297,77 @@ filename() = string() <tag><c><![CDATA[stream]]></c></tag> <item> <p>Streams the body of a 200 or 206 response to the calling - process or to a file. When streaming to the calling process - using the option <c>self</c> the following stream messages - will be sent to that process: <c>{http, {RequestId, - stream_start, Headers}, {http, {RequestId, stream, - BinBodyPart}, {http, {RequestId, stream_end, Headers}</c>. When - streaming to to the calling processes using the option - <c>{self, once}</c> the first message will have an additional - element e.i. <c>{http, {RequestId, stream_start, Headers, Pid}</c>, - this is the process id that should be used as an argument to - <c>http:stream_next/1</c> to trigger the next message to be sent to - the calling process. </p> + process or to a file. When streaming to the calling process + using the option <c>self</c> the following stream messages + will be sent to that process: <c>{http, {RequestId, + stream_start, Headers}, {http, {RequestId, stream, + BinBodyPart}, {http, {RequestId, stream_end, Headers}</c>. When + streaming to to the calling processes using the option + <c>{self, once}</c> the first message will have an additional + element e.i. <c>{http, {RequestId, stream_start, Headers, Pid}</c>, + this is the process id that should be used as an argument to + <c>http:stream_next/1</c> to trigger the next message to be sent to + the calling process. </p> <p>Note that it is possible that chunked encoding will add - headers so that there are more headers in the <c>stream_end</c> - message than in the <c>stream_start</c>. - When streaming to a file and the request is asynchronous the - message <c>{http, {RequestId, saved_to_file}}</c> will be sent. </p> + headers so that there are more headers in the <c>stream_end</c> + message than in the <c>stream_start</c>. + When streaming to a file and the request is asynchronous the + message <c>{http, {RequestId, saved_to_file}}</c> will be sent. </p> <p>Defaults to <c>none</c>. </p> </item> <tag><c><![CDATA[body_format]]></c></tag> <item> <p>Defines if the body shall be delivered as a string or as a - binary. This option is only valid for the synchronous - request. </p> + binary. This option is only valid for the synchronous + request. </p> <p>Defaults to <c>string</c>. </p> </item> <tag><c><![CDATA[full_result]]></c></tag> <item> <p>Should a "full result" be returned to the caller (that is, - the body, the headers and the entire status-line) or not - (the body and the status code). </p> + the body, the headers and the entire status-line) or not + (the body and the status code). </p> <p>Defaults to <c>true</c>. </p> </item> <tag><c><![CDATA[header_as_is]]></c></tag> <item> <p>Shall the headers provided by the user be made - lower case or be regarded as case sensitive. </p> + lower case or be regarded as case sensitive. </p> <p>Note that the http standard requires them to be - case insenstive. This feature should only be used if there is - no other way to communicate with the server or for testing - purpose. Also note that when this option is used no headers - will be automatically added, all necessary headers have to be - provided by the user. </p> - <p>Defaults to <c>false</c>. </p> + case insenstive. This feature should only be used if there is + no other way to communicate with the server or for testing + purpose. Also note that when this option is used no headers + will be automatically added, all necessary headers have to be + provided by the user. </p> + <p>Defaults to <c>false</c>. </p> </item> <tag><c><![CDATA[socket_opts]]></c></tag> <item> <p>Socket options to be used for this and subsequent - request(s). </p> - <p>Overrides any value set by the - <seealso marker="#set_options">set_options</seealso> - function. </p> + request(s). </p> + <p>Overrides any value set by the + <seealso marker="#set_options">set_options</seealso> + function. </p> <p>Note that the validity of the options are <em>not</em> - checked in any way. </p> + checked in any way. </p> <p>Note that this may change the socket behaviour - (see <seealso marker="kernel:inet#setopts/2">inet:setopts/2</seealso>) - for an already existing one, and therefore an already connected - request handler. </p> + (see <seealso marker="kernel:inet#setopts/2">inet:setopts/2</seealso>) + for an already existing one, and therefore an already connected + request handler. </p> <p>By default the socket options set by the - <seealso marker="#set_options">set_options/1,2</seealso> - function are used when establishing a connection. </p> + <seealso marker="#set_options">set_options/1,2</seealso> + function are used when establishing a connection. </p> </item> <tag><c><![CDATA[receiver]]></c></tag> <item> <p>Defines how the client will deliver the result of an - asynchroneous request (<c>sync</c> has the value - <c>false</c>). </p> + asynchroneous request (<c>sync</c> has the value + <c>false</c>). </p> <taglist> <tag><c><![CDATA[pid()]]></c></tag> @@ -380,7 +381,7 @@ filename() = string() <tag><c><![CDATA[function/1]]></c></tag> <item> <p>Information will be delivered to the receiver via calls - to the provided fun: </p> + to the provided fun: </p> <pre> Receiver(ReplyInfo) </pre> @@ -389,7 +390,7 @@ Receiver(ReplyInfo) <tag><c><![CDATA[{Module, Funcion, Args}]]></c></tag> <item> <p>Information will be delivered to the receiver via calls - to the callback function: </p> + to the callback function: </p> <pre> apply(Module, Function, [ReplyInfo | Args]) </pre> @@ -410,7 +411,7 @@ apply(Module, Function, [ReplyInfo | Args]) </pre> <p>Defaults to the <c>pid()</c> of the process calling the request - function (<c>self()</c>). </p> + function (<c>self()</c>). </p> </item> </taglist> @@ -425,7 +426,7 @@ apply(Module, Function, [ReplyInfo | Args]) <type> <v>RequestId = request_id() - A unique identifier as returned by request/4</v> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> </type> <desc> <p>Cancels an asynchronous HTTP-request. </p> @@ -514,11 +515,10 @@ apply(Module, Function, [ReplyInfo | Args]) This option is used to switch on (or off) different levels of erlang trace on the client. It is a debug feature.</d> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> </type> <desc> - <p>Sets options to be used for subsequent - requests.</p> + <p>Sets options to be used for subsequent requests.</p> <note> <p>If possible the client will keep its connections alive and use persistent connections @@ -548,7 +548,7 @@ apply(Module, Function, [ReplyInfo | Args]) </type> <desc> <p>Triggers the next message to be streamed, e.i. - same behavior as active once for sockets.</p> + same behavior as active once for sockets. </p> <marker id="verify_cookies"></marker> <marker id="store_cookies"></marker> @@ -562,14 +562,14 @@ apply(Module, Function, [ReplyInfo | Args]) <type> <v>SetCookieHeaders = headers() - where field = "set-cookie"</v> <v>Url = url()</v> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> </type> <desc> <p>Saves the cookies defined in SetCookieHeaders - in the client profile's cookie database. You need to - call this function if you have set the option <c>cookies</c> to <c>verify</c>. - If no profile is specified the default profile will be used. - </p> + in the client profile's cookie database. You need to + call this function if you have set the option <c>cookies</c> + to <c>verify</c>. + If no profile is specified the default profile will be used. </p> <marker id="cookie_header"></marker> </desc> @@ -582,13 +582,12 @@ apply(Module, Function, [ReplyInfo | Args]) making a request to Url using the profile <c>Profile</c>.</fsummary> <type> <v>Url = url()</v> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> </type> <desc> <p>Returns the cookie header that would be sent - when making a request to <c>Url</c> using the profile <c>Profile</c>. - If no profile is specified the default profile will be used. - </p> + when making a request to <c>Url</c> using the profile <c>Profile</c>. + If no profile is specified the default profile will be used. </p> <marker id="reset_cookies"></marker> </desc> @@ -600,12 +599,12 @@ apply(Module, Function, [ReplyInfo | Args]) <name>reset_cookies(Profile) -> void()</name> <fsummary>Reset the cookie database.</fsummary> <type> - <v>Profile = profile()</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> </type> <desc> - <p>Resets (clears) the cookie database for the specified <c>Profile</c>. - If no profile is specified the default profile will be used. - </p> + <p>Resets (clears) the cookie database for the specified + <c>Profile</c>. If no profile is specified the default profile + will be used. </p> </desc> </func> @@ -615,17 +614,16 @@ apply(Module, Function, [ReplyInfo | Args]) <name>which_cookies(Profile) -> cookies()</name> <fsummary>Dumps out the entire cookie database.</fsummary> <type> - <v>Profile = profile()</v> - <v>cookies() = [cooie_stores()]</v> - <v>cookie_stores() = {cookies, icookies()} | {session_cookies, icookies()}</v> - <v>icookies() = [icookie()]</v> + <v>Profile = profile() | pid() (when started <c>stand_alone</c>)</v> + <v>cookies() = [cookie_stores()]</v> + <v>cookie_stores() = {cookies, cookies()} | {session_cookies, cookies()}</v> + <v>cookies() = [cookie()]</v> <v>cookie() = term()</v> </type> <desc> <p>This function produces a list of the entire cookie database. - It is intended for debugging/testing purposes. - If no profile is specified the default profile will be used. - </p> + It is intended for debugging/testing purposes. + If no profile is specified the default profile will be used. </p> </desc> </func> </funcs> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 0926df8581..34f26bf45b 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,6 +32,63 @@ <file>notes.xml</file> </header> + <section><title>Inets 5.7</title> + + <section><title>Improvements and New Features</title> +<!-- + <p>-</p> +--> + + <list> + <item> + <p>[httpc|httpd] Added support for IPv6 with ssl. </p> + <p>Own Id: OTP-5566</p> + </item> + + </list> + + </section> + + <section><title>Fixed Bugs and Malfunctions</title> +<!-- + <p>-</p> +--> + + <list> + <item> + <p>[httpc] Remove unnecessary usage of iolist_to_binary when + processing body (for PUT and POST). </p> + <p>Filipe David Manana</p> + <p>Own Id: OTP-9317</p> + </item> + + <item> + <p>[ftp] FTP client doesn't work with IPv6 host.</p> + <p>Attila Rajmund Nohl</p> + <p>Own Id: OTP-9342 Aux Id: seq11853</p> + </item> + + <item> + <p>[httpd] Peer/sockname resolv doesn't work with IPv6 addrs + in HTTP. </p> + <p>Attila Rajmund Nohl.</p> + <p>Own Id: OTP-9343</p> + </item> + + <item> + <p>[httpc] Clients started stand-alone not properly handled. + Also it was not documented how to use them, that is that + once started, they are represented by a <c>pid()</c> and not by + their <c>profile()</c>. </p> + <p>Own Id: OTP-9365</p> + </item> + + </list> + </section> + + </section> <!-- 5.7 --> + + <section><title>Inets 5.6</title> <section><title>Improvements and New Features</title> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index fe6cb0c191..ac72963347 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -1038,10 +1038,12 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> Port = key_search(port, Opts, ?FTP_PORT), Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), Progress = key_search(progress, Opts, ignore), + IpFamily = key_search(ipfamily, Opts, inet), State2 = State#state{client = From, mode = Mode, - progress = progress(Progress)}, + progress = progress(Progress), + ipfamily = IpFamily}, ?fcrd("handle_call(open) -> setup ctrl connection with", [{host, Host}, {port, Port}, {timeout, Timeout}]), diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index 6ffa5e8ba5..fe8e93af1f 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -64,17 +64,16 @@ default_profile() -> profile_name(?DEFAULT_PROFILE) -> httpc_manager; +profile_name(Profile) when is_pid(Profile) -> + Profile; profile_name(Profile) -> - profile_name("httpc_manager_", Profile). + Prefix = lists:flatten(io_lib:format("~w_", [?MODULE])), + profile_name(Prefix, Profile). profile_name(Prefix, Profile) when is_atom(Profile) -> list_to_atom(Prefix ++ atom_to_list(Profile)); -profile_name(Prefix, Profile) when is_pid(Profile) -> - ProfileStr0 = - string:strip(string:strip(erlang:pid_to_list(Profile), left, $<), right, $>), - F = fun($.) -> $_; (X) -> X end, - ProfileStr = [F(C) || C <- ProfileStr0], - list_to_atom(Prefix ++ "pid_" ++ ProfileStr). +profile_name(_Prefix, Profile) when is_pid(Profile) -> + Profile. %%-------------------------------------------------------------------------- @@ -115,9 +114,11 @@ request(Url, Profile) -> %% {keyfile, path()} | {password, string()} | {cacertfile, path()} | %% {ciphers, string()} %% Options - [Option] -%% Option - {sync, Boolean} | {body_format, BodyFormat} | -%% {full_result, Boolean} | {stream, To} | -%% {headers_as_is, Boolean} +%% Option - {sync, Boolean} | +%% {body_format, BodyFormat} | +%% {full_result, Boolean} | +%% {stream, To} | +%% {headers_as_is, Boolean} %% StatusLine = {HTTPVersion, StatusCode, ReasonPhrase}</v> %% HTTPVersion = string() %% StatusCode = integer() @@ -518,17 +519,15 @@ mk_chunkify_fun(ProcessBody) -> eof -> {ok, <<"0\r\n\r\n">>, eof_body}; {ok, Data, NewAcc} -> - {ok, mk_chunk_bin(Data), NewAcc} + Chunk = [ + integer_to_list(iolist_size(Data), 16), + "\r\n", + Data, + "\r\n"], + {ok, Chunk, NewAcc} end end. -mk_chunk_bin(Data) -> - Bin = iolist_to_binary(Data), - iolist_to_binary([hex_size(Bin), "\r\n", Bin, "\r\n"]). - -hex_size(Bin) -> - hd(io_lib:format("~.16B", [size(Bin)])). - handle_answer(RequestId, false, _) -> {ok, RequestId}; @@ -552,9 +551,7 @@ return_answer(Options, {{"HTTP/0.9",_,_}, _, BinBody}) -> {ok, Body}; return_answer(Options, {StatusLine, Headers, BinBody}) -> - Body = maybe_format_body(BinBody, Options), - case proplists:get_value(full_result, Options, true) of true -> {ok, {StatusLine, Headers, Body}}; diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 1f0e012e7e..9ac9ee6f7b 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -515,7 +515,7 @@ handle_info({Proto, _Socket, Data}, {stop, normal, NewState} end, - ?hcri("data processed", []), + ?hcri("data processed", [{final_result, FinalResult}]), FinalResult; @@ -629,8 +629,9 @@ handle_info(timeout_queue, #state{timers = Timers} = State) -> Timers#timers{queue_timer = undefined}}}; %% Setting up the connection to the server somehow failed. -handle_info({init_error, _, ClientErrMsg}, +handle_info({init_error, Tag, ClientErrMsg}, State = #state{request = Request}) -> + ?hcrv("init error", [{tag, Tag}, {client_error, ClientErrMsg}]), NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState}; @@ -707,9 +708,9 @@ terminate(normal, %% And, just in case, close our side (**really** overkill) http_transport:close(SocketType, Socket); -terminate(Reason, #state{session = #session{id = Id, - socket = Socket, - socket_type = SocketType}, +terminate(Reason, #state{session = #session{id = Id, + socket = Socket, + socket_type = SocketType}, request = undefined, profile_name = ProfileName, timers = Timers, @@ -1403,7 +1404,7 @@ try_to_enable_pipeline_or_keep_alive( answer_request(#request{id = RequestId, from = From} = Request, Msg, #state{timers = Timers, profile_name = ProfileName} = State) -> - ?hcrt("answer request", [{request, Request}]), + ?hcrt("answer request", [{request, Request}, {msg, Msg}]), httpc_response:send(From, Msg), RequestTimers = Timers#timers.request_timers, TimerRef = diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index 7f66b477eb..9015bf1ce2 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -52,7 +52,7 @@ cancel = [], % [{RequestId, HandlerPid, ClientPid}] handler_db, % ets() - Entry: #handler_info{} cookie_db, % cookie_db() - session_db, % ets() - Entry: #tcp_session{} + session_db, % ets() - Entry: #session{} profile_name, % atom() options = #options{} }). @@ -178,7 +178,7 @@ request_done(RequestId, ProfileName) -> %%-------------------------------------------------------------------- %% Function: insert_session(Session, ProfileName) -> _ -%% Session - #tcp_session{} +%% Session - #session{} %% ProfileName - atom() %% %% Description: Inserts session information into the httpc manager @@ -669,7 +669,7 @@ select_session(Method, HostPort, Scheme, SessionType, (SessionType =:= keep_alive) of true -> %% Look for handlers connecting to this host (HostPort) - %% tcp_session with record name field (tcp_session) and + %% session with record name field (session) and %% socket fields ignored. The fields id (part of: HostPort), %% client_close, scheme and type specified. %% The fields id (part of: HandlerPid) and queue_length diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 01b51d531a..9b8190ebed 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -33,8 +33,8 @@ peername/2, sockname/2, resolve/0 ]). - -export([negotiate/3]). +-export([ipv4_name/1, ipv6_name/1]). -include_lib("inets/src/inets_app/inets_internal.hrl"). -include("http_internal.hrl"). @@ -142,8 +142,8 @@ connect({ossl, SslConfig}, {Host, Port}, _, Timeout) -> ERROR end; -connect({essl, SslConfig}, {Host, Port}, _, Timeout) -> - Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig, +connect({essl, SslConfig}, {Host, Port}, Opts0, Timeout) -> + Opts = [binary, {active, false}, {ssl_imp, new} | Opts0] ++ SslConfig, ?hlrt("connect using essl", [{host, Host}, {port, Port}, @@ -176,8 +176,8 @@ connect({essl, SslConfig}, {Host, Port}, _, Timeout) -> listen(SocketType, Port) -> listen(SocketType, undefined, Port). -listen(ip_comm = SocketType, Addr, Port) -> - listen(SocketType, Addr, Port, undefined); +listen(ip_comm = _SocketType, Addr, Port) -> + listen_ip_comm(Addr, Port, undefined); %% Wrapper for backaward compatibillity listen({ssl, SSLConfig}, Addr, Port) -> @@ -187,35 +187,33 @@ listen({ssl, SSLConfig}, Addr, Port) -> {ssl_config, SSLConfig}]), listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port); -listen({ossl, SSLConfig} = Ssl, Addr, Port) -> +listen({ossl, SSLConfig}, Addr, Port) -> ?hlrt("listen (ossl)", [{addr, Addr}, {port, Port}, {ssl_config, SSLConfig}]), - Opt = sock_opt(Ssl, Addr, SSLConfig), - ?hlrt("listen options", [{opt, Opt}]), - ssl:listen(Port, [{ssl_imp, old} | Opt]); + listen_ssl(Addr, Port, [{ssl_imp, old} | SSLConfig]); -listen({essl, SSLConfig} = Ssl, Addr, Port) -> +listen({essl, SSLConfig}, Addr, Port) -> ?hlrt("listen (essl)", [{addr, Addr}, {port, Port}, {ssl_config, SSLConfig}]), - Opt = sock_opt(Ssl, Addr, SSLConfig), - ?hlrt("listen options", [{opt, Opt}]), - Opt2 = [{ssl_imp, new}, {reuseaddr, true} | Opt], - ssl:listen(Port, Opt2). + listen_ssl(Addr, Port, [{ssl_imp, new}, {reuseaddr, true} | SSLConfig]). + listen(ip_comm, Addr, Port, Fd) -> - case (catch listen_ip_comm(Addr, Port, Fd)) of + listen_ip_comm(Addr, Port, Fd). + +listen_ip_comm(Addr, Port, Fd) -> + case (catch do_listen_ip_comm(Addr, Port, Fd)) of {'EXIT', Reason} -> {error, {exit, Reason}}; Else -> Else end. - -listen_ip_comm(Addr, Port, Fd) -> +do_listen_ip_comm(Addr, Port, Fd) -> {NewPort, Opts, IpFamily} = get_socket_info(Addr, Port, Fd), case IpFamily of inet6fb4 -> @@ -248,6 +246,41 @@ listen_ip_comm(Addr, Port, Fd) -> gen_tcp:listen(NewPort, Opts2) end. + +listen_ssl(Addr, Port, Opts0) -> + IpFamily = ipfamily_default(Addr, Port), + BaseOpts = [{backlog, 128}, {reuseaddr, true} | Opts0], + Opts = sock_opts(Addr, BaseOpts), + case IpFamily of + inet6fb4 -> + Opts2 = [inet6 | Opts], + ?hlrt("try ipv6 listen", [{opts, Opts2}]), + case (catch ssl:listen(Port, Opts2)) of + {error, Reason} when ((Reason =:= nxdomain) orelse + (Reason =:= eafnosupport)) -> + Opts3 = [inet | Opts], + ?hlrt("ipv6 listen failed - try ipv4 instead", + [{reason, Reason}, {opts, Opts3}]), + ssl:listen(Port, Opts3); + + {'EXIT', Reason} -> + Opts3 = [inet | Opts], + ?hlrt("ipv6 listen exit - try ipv4 instead", + [{reason, Reason}, {opts, Opts3}]), + ssl:listen(Port, Opts3); + + Other -> + ?hlrt("ipv6 listen done", [{other, Other}]), + Other + end; + + _ -> + Opts2 = [IpFamily | Opts], + ?hlrt("listen", [{opts, Opts2}]), + ssl:listen(Port, Opts2) + end. + + ipfamily_default(Addr, Port) -> httpd_conf:lookup(Addr, Port, ipfamily, inet6fb4). @@ -257,9 +290,9 @@ get_socket_info(Addr, Port, Fd0) -> %% The presence of a file descriptor takes precedence case get_fd(Port, Fd0, IpFamilyDefault) of {Fd, IpFamily} -> - {0, sock_opt(ip_comm, Addr, [{fd, Fd} | BaseOpts]), IpFamily}; + {0, sock_opts(Addr, [{fd, Fd} | BaseOpts]), IpFamily}; undefined -> - {Port, sock_opt(ip_comm, Addr, BaseOpts), IpFamilyDefault} + {Port, sock_opts(Addr, BaseOpts), IpFamilyDefault} end. get_fd(Port, undefined = _Fd, IpFamilyDefault) -> @@ -499,44 +532,28 @@ close({essl, _}, Socket) -> %% connection, usning either gen_tcp or ssl. %%------------------------------------------------------------------------- peername(ip_comm, Socket) -> - case inet:peername(Socket) of - {ok,{{A, B, C, D}, Port}} -> - PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ - integer_to_list(C)++"."++integer_to_list(D), - {Port, PeerName}; - {ok,{{A, B, C, D, E, F, G, H}, Port}} -> - PeerName = http_util:integer_to_hexlist(A) ++ ":"++ - http_util:integer_to_hexlist(B) ++ ":" ++ - http_util:integer_to_hexlist(C) ++ ":" ++ - http_util:integer_to_hexlist(D) ++ ":" ++ - http_util:integer_to_hexlist(E) ++ ":" ++ - http_util:integer_to_hexlist(F) ++ ":" ++ - http_util:integer_to_hexlist(G) ++":"++ - http_util:integer_to_hexlist(H), - {Port, PeerName}; - {error, _} -> - {-1, "unknown"} - end; + do_peername(inet:peername(Socket)); %% Wrapper for backaward compatibillity peername({ssl, SSLConfig}, Socket) -> peername({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); peername({ossl, _}, Socket) -> - peername_ssl(Socket); + do_peername(ssl:peername(Socket)); peername({essl, _}, Socket) -> - peername_ssl(Socket). - -peername_ssl(Socket) -> - case ssl:peername(Socket) of - {ok,{{A, B, C, D}, Port}} -> - PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ - integer_to_list(C)++"."++integer_to_list(D), - {Port, PeerName}; - {error, _} -> - {-1, "unknown"} - end. + do_peername(ssl:peername(Socket)). + +do_peername({ok, {Addr, Port}}) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + PeerName = ipv4_name(Addr), + {Port, PeerName}; +do_peername({ok, {Addr, Port}}) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + PeerName = ipv6_name(Addr), + {Port, PeerName}; +do_peername({error, _}) -> + {-1, "unknown"}. %%------------------------------------------------------------------------- @@ -550,44 +567,28 @@ peername_ssl(Socket) -> %% other end of connection, using either gen_tcp or ssl. %%------------------------------------------------------------------------- sockname(ip_comm, Socket) -> - case inet:sockname(Socket) of - {ok,{{A, B, C, D}, Port}} -> - SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++ - integer_to_list(C)++"."++integer_to_list(D), - {Port, SockName}; - {ok,{{A, B, C, D, E, F, G, H}, Port}} -> - SockName = http_util:integer_to_hexlist(A) ++ ":"++ - http_util:integer_to_hexlist(B) ++ ":" ++ - http_util:integer_to_hexlist(C) ++ ":" ++ - http_util:integer_to_hexlist(D) ++ ":" ++ - http_util:integer_to_hexlist(E) ++ ":" ++ - http_util:integer_to_hexlist(F) ++ ":" ++ - http_util:integer_to_hexlist(G) ++":"++ - http_util:integer_to_hexlist(H), - {Port, SockName}; - {error, _} -> - {-1, "unknown"} - end; + do_sockname(inet:sockname(Socket)); %% Wrapper for backaward compatibillity sockname({ssl, SSLConfig}, Socket) -> sockname({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket); sockname({ossl, _}, Socket) -> - sockname_ssl(Socket); + do_sockname(ssl:sockname(Socket)); sockname({essl, _}, Socket) -> - sockname_ssl(Socket). - -sockname_ssl(Socket) -> - case ssl:sockname(Socket) of - {ok,{{A, B, C, D}, Port}} -> - SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++ - integer_to_list(C)++"."++integer_to_list(D), - {Port, SockName}; - {error, _} -> - {-1, "unknown"} - end. + do_sockname(ssl:sockname(Socket)). + +do_sockname({ok, {Addr, Port}}) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + SockName = ipv4_name(Addr), + {Port, SockName}; +do_sockname({ok, {Addr, Port}}) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + SockName = ipv6_name(Addr), + {Port, SockName}; +do_sockname({error, _}) -> + {-1, "unknown"}. %%------------------------------------------------------------------------- @@ -601,29 +602,49 @@ resolve() -> Name. +%%------------------------------------------------------------------------- +%% ipv4_name(Ipv4Addr) -> string() +%% ipv6_name(Ipv6Addr) -> string() +%% Ipv4Addr = ip4_address() +%% Ipv6Addr = ip6_address() +%% +%% Description: Returns the local hostname. +%%------------------------------------------------------------------------- +ipv4_name({A, B, C, D}) -> + integer_to_list(A) ++ "." ++ + integer_to_list(B) ++ "." ++ + integer_to_list(C) ++ "." ++ + integer_to_list(D). + +ipv6_name({A, B, C, D, E, F, G, H}) -> + http_util:integer_to_hexlist(A) ++ ":"++ + http_util:integer_to_hexlist(B) ++ ":" ++ + http_util:integer_to_hexlist(C) ++ ":" ++ + http_util:integer_to_hexlist(D) ++ ":" ++ + http_util:integer_to_hexlist(E) ++ ":" ++ + http_util:integer_to_hexlist(F) ++ ":" ++ + http_util:integer_to_hexlist(G) ++ ":" ++ + http_util:integer_to_hexlist(H). + + %%%======================================================================== %%% Internal functions %%%======================================================================== +%% -- sock_opts -- %% Address any comes from directive: BindAddress "*" -sock_opt(ip_comm, any = Addr, Opts) -> - sock_opt2([{ip, Addr} | Opts]); -sock_opt(ip_comm, undefined, Opts) -> - sock_opt2(Opts); -sock_opt(_, any = _Addr, Opts) -> - sock_opt2(Opts); -sock_opt(_, undefined = _Addr, Opts) -> - sock_opt2(Opts); -sock_opt(_, {_,_,_,_} = Addr, Opts) -> - sock_opt2([{ip, Addr} | Opts]); -sock_opt(ip_comm, Addr, Opts) -> - sock_opt2([{ip, Addr} | Opts]); -sock_opt(_, Addr, Opts) -> - sock_opt2([{ip, Addr} | Opts]). - -sock_opt2(Opts) -> +sock_opts(undefined, Opts) -> + sock_opts(Opts); +sock_opts(any = Addr, Opts) -> + sock_opts([{ip, Addr} | Opts]); +sock_opts(Addr, Opts) -> + sock_opts([{ip, Addr} | Opts]). + +sock_opts(Opts) -> [{packet, 0}, {active, false} | Opts]. + +%% -- negotiate -- negotiate(ip_comm,_,_) -> ?hlrt("negotiate(ip_comm)", []), ok; diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 47f3fbba58..8b0fcb185d 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,6 +18,15 @@ {"%VSN%", [ + {"5.6", + [ + {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, + {load_module, http_transport, soft_purge, soft_purge, [http_transport]}, + {update, httpc_handler, soft, soft_purge, soft_purge, []}, + {update, httpc_manager, soft, soft_purge, soft_purge, [httpc_handler]}, + {update, ftp, soft, soft_purge, soft_purge, []} + ] + }, {"5.5.2", [ {restart_application, inets} @@ -40,6 +49,15 @@ } ], [ + {"5.6", + [ + {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, + {load_module, http_transport, soft_purge, soft_purge, [http_transport]}, + {update, httpc_handler, soft, soft_purge, soft_purge, []}, + {update, httpc_manager, soft, soft_purge, soft_purge, [httpc_handler]}, + {update, ftp, soft, soft_purge, soft_purge, []} + ] + }, {"5.5.2", [ {restart_application, inets} diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl index d0d07a8358..3ebd02229e 100644 --- a/lib/inets/test/ftp_suite_lib.erl +++ b/lib/inets/test/ftp_suite_lib.erl @@ -1129,10 +1129,16 @@ ticket_6035(Config) -> LogFile = filename:join([PrivDir,"ticket_6035.log"]), try begin + p("ticket_6035 -> select ftpd host"), Host = dirty_select_ftpd_host(Config), + p("ticket_6035 -> ftpd host selected (~p) => now spawn ftp owner", [Host]), Pid = spawn(?MODULE, open_wait_6035, [Host, self()]), + p("ticket_6035 -> waiter spawned: ~p => now open error logfile (~p)", + [Pid, LogFile]), error_logger:logfile({open, LogFile}), - ok = kill_ftp_proc_6035(Pid,LogFile), + p("ticket_6035 -> error logfile open => now kill waiter process"), + true = kill_ftp_proc_6035(Pid, LogFile), + p("ticket_6035 -> waiter process killed => now close error logfile"), error_logger:logfile(close), p("ticket_6035 -> done", []), ok @@ -1146,7 +1152,7 @@ kill_ftp_proc_6035(Pid, LogFile) -> p("kill_ftp_proc_6035 -> entry"), receive open -> - p("kill_ftp_proc_6035 -> received open: send shutdown"), + p("kill_ftp_proc_6035 -> received open => now issue shutdown"), exit(Pid, shutdown), kill_ftp_proc_6035(Pid, LogFile); {open_failed, Reason} -> @@ -1159,11 +1165,11 @@ kill_ftp_proc_6035(Pid, LogFile) -> is_error_report_6035(LogFile) end. -open_wait_6035(FtpServer, From) -> - p("open_wait_6035 -> try connect to ~s", [FtpServer]), +open_wait_6035({Tag, FtpServer}, From) -> + p("open_wait_6035 -> try connect to [~p] ~s for ~p", [Tag, FtpServer, From]), case ftp:open(FtpServer, [{timeout, timer:seconds(15)}]) of {ok, Pid} -> - p("open_wait_6035 -> connected, now login"), + p("open_wait_6035 -> connected (~p), now login", [Pid]), LoginResult = ftp:user(Pid,"anonymous","kldjf"), p("open_wait_6035 -> login result: ~p", [LoginResult]), From ! open, @@ -1191,22 +1197,27 @@ is_error_report_6035(LogFile) -> Res = case file:read_file(LogFile) of {ok, Bin} -> - p("is_error_report_6035 -> logfile read"), - read_log_6035(binary_to_list(Bin)); + Txt = binary_to_list(Bin), + p("is_error_report_6035 -> logfile read: ~n~p", [Txt]), + read_log_6035(Txt); _ -> - ok + false end, p("is_error_report_6035 -> logfile read result: " "~n ~p", [Res]), - file:delete(LogFile), + %% file:delete(LogFile), Res. read_log_6035("=ERROR REPORT===="++_Rest) -> - error_report; -read_log_6035([_H|T]) -> + p("read_log_6035 -> ERROR REPORT detected"), + true; +read_log_6035([H|T]) -> + p("read_log_6035 -> OTHER: " + "~p", [H]), read_log_6035(T); read_log_6035([]) -> - ok. + p("read_log_6035 -> done"), + false. %%-------------------------------------------------------------------- diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 1998bd3950..6edd5371af 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -64,16 +64,6 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [ - proxy_options, - proxy_head, - proxy_get, - proxy_trace, - proxy_post, - proxy_put, - proxy_delete, - proxy_auth, - proxy_headers, - proxy_emulate_lower_versions, http_options, http_head, http_get, @@ -88,15 +78,6 @@ all() -> http_headers, http_headers_dummy, http_bad_response, - ssl_head, - ossl_head, - essl_head, - ssl_get, - ossl_get, - essl_get, - ssl_trace, - ossl_trace, - essl_trace, http_redirect, http_redirect_loop, http_internal_server_error, @@ -106,21 +87,44 @@ all() -> http_emulate_lower_versions, http_relaxed, page_does_not_exist, - proxy_page_does_not_exist, - proxy_https_not_supported, - http_stream, - http_stream_once, - proxy_stream, parse_url, options, - ipv6, headers_as_is, + {group, proxy}, + {group, ssl}, + {group, stream}, + {group, ipv6}, {group, tickets}, initial_server_connect ]. groups() -> - [{tickets, [], [hexed_query_otp_6191, + [ + {proxy, [], [proxy_options, + proxy_head, + proxy_get, + proxy_trace, + proxy_post, + proxy_put, + proxy_delete, + proxy_auth, + proxy_headers, + proxy_emulate_lower_versions, + proxy_page_does_not_exist, + proxy_https_not_supported]}, + {ssl, [], [ssl_head, + ossl_head, + essl_head, + ssl_get, + ossl_get, + essl_get, + ssl_trace, + ossl_trace, + essl_trace]}, + {stream, [], [http_stream, + http_stream_once, + proxy_stream]}, + {tickets, [], [hexed_query_otp_6191, empty_body_otp_6243, empty_response_header_otp_6830, transfer_encoding_otp_6807, @@ -139,7 +143,10 @@ groups() -> {otp_8154, [], [otp_8154_1]}, {otp_8106, [], [otp_8106_pid, otp_8106_fun, - otp_8106_mfa]}]. + otp_8106_mfa]}, + {ipv6, [], [ipv6_ipcomm, ipv6_essl]} + ]. + init_per_group(_GroupName, Config) -> @@ -213,36 +220,38 @@ end_per_suite(Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- + init_per_testcase(otp_8154_1 = Case, Config) -> init_per_testcase(Case, 5, Config); -init_per_testcase(initial_server_connect, Config) -> +init_per_testcase(initial_server_connect = Case, Config) -> %% Try to check if crypto actually exist or not, %% this test case does not work unless it does - case (catch crypto:start()) of - ok -> - application:start(public_key), - application:start(ssl), + try + begin + ensure_started(crypto), + ensure_started(public_key), + ensure_started(ssl), inets:start(), - Config; - _ -> - {skip,"Could not start crypto"} + Config + end + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} end; init_per_testcase(Case, Config) -> init_per_testcase(Case, 2, Config). -init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> - tsp("init_per_testcase_ssl -> stop ssl"), - application:stop(ssl), - Config2 = lists:keydelete(local_ssl_server, 1, Config), - %% Will start inets - tsp("init_per_testcase_ssl -> try start http server (including inets)"), - Server = inets_test_lib:start_http_server( - filename:join(PrivDir, SslConfFile), Tag), - tsp("init_per_testcase -> Server: ~p", [Server]), - [{local_ssl_server, Server} | Config2]. - init_per_testcase(Case, Timeout, Config) -> io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n", [?MODULE, Case, Timeout]), @@ -261,13 +270,16 @@ init_per_testcase(Case, Timeout, Config) -> NewConfig = case atom_to_list(Case) of [$s, $s, $l | _] -> - init_per_testcase_ssl(ssl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(ssl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); [$o, $s, $s, $l | _] -> - init_per_testcase_ssl(ossl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(ossl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); [$e, $s, $s, $l | _] -> - init_per_testcase_ssl(essl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(essl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); "proxy_" ++ Rest -> io:format("init_per_testcase -> Rest: ~p~n", [Rest]), @@ -275,16 +287,23 @@ init_per_testcase(Case, Timeout, Config) -> "https_not_supported" -> tsp("init_per_testcase -> [proxy case] start inets"), inets:start(), - tsp("init_per_testcase -> [proxy case] start ssl"), - application:start(crypto), - application:start(public_key), - case (catch application:start(ssl)) of + tsp("init_per_testcase -> " + "[proxy case] start crypto, public_key and ssl"), + try ensure_started([crypto, public_key, ssl]) of ok -> - [{watchdog, Dog} | TmpConfig]; - _ -> - [{skip, "SSL does not seem to be supported"} - | TmpConfig] + [{watchdog, Dog} | TmpConfig] + catch + throw:{error, {failed_starting, App, _}} -> + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} end; + _ -> %% We use erlang.org for the proxy tests %% and after the switch to erlang-web, many @@ -321,6 +340,33 @@ init_per_testcase(Case, Timeout, Config) -> [{skip, "proxy not responding"} | TmpConfig] end end; + + "ipv6_" ++ _Rest -> + %% Ensure needed apps (crypto, public_key and ssl) started + try ensure_started([crypto, public_key, ssl]) of + ok -> + Profile = ipv6, + %% A stand-alone profile is represented by a pid() + {ok, ProfilePid} = + inets:start(httpc, + [{profile, Profile}, + {data_dir, PrivDir}], stand_alone), + httpc:set_options([{ipfamily, inet6}], ProfilePid), + tsp("httpc profile pid: ~p", [ProfilePid]), + [{watchdog, Dog}, {profile, ProfilePid}| TmpConfig] + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} + end; _ -> TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig), Server = @@ -330,9 +376,7 @@ init_per_testcase(Case, Timeout, Config) -> [{watchdog, Dog}, {local_server, Server} | TmpConfig2] end, - %% httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, - %% ["localhost", ?IPV6_LOCAL_HOST]}}]), - + %% This will fail for the ipv6_ - cases (but that is ok) httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, ["localhost", ?IPV6_LOCAL_HOST]}}, {ipfamily, inet6fb4}]), @@ -341,6 +385,19 @@ init_per_testcase(Case, Timeout, Config) -> NewConfig. +init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> + tsp("init_per_testcase_ssl(~w) -> stop ssl", [Tag]), + application:stop(ssl), + Config2 = lists:keydelete(local_ssl_server, 1, Config), + %% Will start inets + tsp("init_per_testcase_ssl(~w) -> try start http server (including inets)", + [Tag]), + Server = inets_test_lib:start_http_server( + filename:join(PrivDir, SslConfFile), Tag), + tsp("init_per_testcase(~w) -> Server: ~p", [Tag, Server]), + [{local_ssl_server, Server} | Config2]. + + %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() @@ -349,13 +406,36 @@ init_per_testcase(Case, Timeout, Config) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(http_save_to_file, Config) -> - PrivDir = ?config(priv_dir, Config), +end_per_testcase(http_save_to_file = Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + PrivDir = ?config(priv_dir, Config), FullPath = filename:join(PrivDir, "dummy.html"), file:delete(FullPath), finish(Config); -end_per_testcase(_, Config) -> +end_per_testcase(Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + case atom_to_list(Case) of + "ipv6_" ++ _Rest -> + tsp("end_per_testcase(~w) -> stop ssl", [Case]), + application:stop(ssl), + tsp("end_per_testcase(~w) -> stop public_key", [Case]), + application:stop(public_key), + tsp("end_per_testcase(~w) -> stop crypto", [Case]), + application:stop(crypto), + ProfilePid = ?config(profile, Config), + tsp("end_per_testcase(~w) -> stop httpc profile (~p)", + [Case, ProfilePid]), + unlink(ProfilePid), + inets:stop(stand_alone, ProfilePid), + tsp("end_per_testcase(~w) -> httpc profile (~p) stopped", + [Case, ProfilePid]), + ok; + _ -> + ok + end, finish(Config). finish(Config) -> @@ -364,6 +444,7 @@ finish(Config) -> undefined -> ok; _ -> + tsp("finish -> stop watchdog (~p)", [Dog]), test_server:timetrap_cancel(Dog) end. @@ -565,7 +646,7 @@ http_relaxed(suite) -> http_relaxed(Config) when is_list(Config) -> ok = httpc:set_options([{ipv6, disabled}]), % also test the old option %% ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_reason_phrase.html", @@ -591,7 +672,7 @@ http_dummy_pipe(suite) -> []; http_dummy_pipe(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html", @@ -905,7 +986,7 @@ http_headers_dummy(suite) -> []; http_headers_dummy(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html", @@ -970,7 +1051,7 @@ http_bad_response(suite) -> []; http_bad_response(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html", @@ -1089,9 +1170,9 @@ ssl_get(SslTag, Config) when is_list(Config) -> httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []), inets_test_lib:check_body(Body); {ok, _} -> - {skip, "Failed to start local http-server"}; + {skip, "local http-server not started"}; _ -> - {skip, "Failed to start SSL"} + {skip, "SSL not started"} end. @@ -1149,9 +1230,9 @@ ssl_trace(SslTag, Config) when is_list(Config) -> tsf({failed, Error}) end; {ok, _} -> - {skip, "Failed to start local http-server"}; + {skip, "local http-server not started"}; _ -> - {skip, "Failed to start SSL"} + {skip, "SSL not started"} end. @@ -1170,7 +1251,7 @@ http_redirect(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), tsp("http_redirect -> start dummy server inet"), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), tsp("http_redirect -> server port = ~p", [Port]), URL300 = ?URL_START ++ integer_to_list(Port) ++ "/300.html", @@ -1282,7 +1363,7 @@ http_redirect_loop(suite) -> []; http_redirect_loop(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html", @@ -1299,7 +1380,7 @@ http_internal_server_error(suite) -> []; http_internal_server_error(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html", @@ -1335,7 +1416,7 @@ http_userinfo(suite) -> http_userinfo(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URLAuth = "http://alladin:sesame@localhost:" ++ integer_to_list(Port) ++ "/userinfo.html", @@ -1361,7 +1442,7 @@ http_cookie(suite) -> []; http_cookie(Config) when is_list(Config) -> ok = httpc:set_options([{cookies, enabled}, {ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URLStart = ?URL_START ++ integer_to_list(Port), @@ -1735,7 +1816,7 @@ http_stream_once(Config) when is_list(Config) -> p("http_stream_once -> set ipfamily to inet", []), ok = httpc:set_options([{ipfamily, inet}]), p("http_stream_once -> start dummy server", []), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), PortStr = integer_to_list(Port), p("http_stream_once -> once", []), @@ -1871,28 +1952,79 @@ parse_url(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -ipv6() -> - [{require,ipv6_hosts}]. -ipv6(doc) -> - ["Test ipv6."]; -ipv6(suite) -> - []; -ipv6(Config) when is_list(Config) -> - {ok, Hostname} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname), - ct:get_config(ipv6_hosts)) of - true -> - {DummyServerPid, Port} = dummy_server(self(), ipv6), - - URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++ + +ipv6_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_ipcomm(doc) -> + ["Test ip_comm ipv6."]; +ipv6_ipcomm(suite) -> + []; +ipv6_ipcomm(Config) when is_list(Config) -> + HTTPOptions = [], + SocketType = ip_comm, + Scheme = "http", + Extra = [], + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6_essl() -> + [{require, ipv6_hosts}]. +ipv6_essl(doc) -> + ["Test essl ipv6."]; +ipv6_essl(suite) -> + []; +ipv6_essl(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = {essl, SSLOptions}, + tsp("ssl_ipv6 -> make request using: " + "~n SSLOptions: ~p", [SSLOptions]), + HTTPOptions = [{ssl, SSLConfig}], + SocketType = essl, + Scheme = "https", + Extra = SSLOptions, + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6(SocketType, Scheme, HTTPOptions, Extra, Config) -> + %% Check if we are a IPv6 host + tsp("ipv6 -> verify ipv6 support", []), + case inets_test_lib:has_ipv6_support(Config) of + {ok, Addr} -> + tsp("ipv6 -> ipv6 supported: ~p", [Addr]), + {DummyServerPid, Port} = dummy_server(SocketType, ipv6, Extra), + Profile = ?config(profile, Config), + URL = + Scheme ++ + "://[" ++ http_transport:ipv6_name(Addr) ++ "]:" ++ integer_to_list(Port) ++ "/foobar.html", - {ok, {{_,200,_}, [_ | _], [_|_]}} = - httpc:request(get, {URL, []}, [], []), - - DummyServerPid ! stop, + tsp("ipv6 -> issue request with: " + "~n URL: ~p" + "~n HTTPOptions: ~p", [URL, HTTPOptions]), + case httpc:request(get, {URL, []}, HTTPOptions, [], Profile) of + {ok, {{_,200,_}, [_ | _], [_|_]}} -> + tsp("ipv6 -> expected result"), + DummyServerPid ! stop, + ok; + {ok, Unexpected} -> + tsp("ipv6 -> unexpected result: " + "~n ~p", [Unexpected]), + DummyServerPid ! stop, + tsf({unexpected_result, Unexpected}); + {error, Reason} -> + tsp("ipv6 -> error: " + "~n Reason: ~p", [Reason]), + DummyServerPid ! stop, + tsf(Reason) + end, ok; - false -> + _ -> + tsp("ipv6 -> ipv6 not supported", []), {skip, "Host does not support IPv6"} end. @@ -1945,7 +2077,7 @@ http_invalid_http(suite) -> []; http_invalid_http(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html", @@ -2002,7 +2134,7 @@ transfer_encoding_otp_6807(suite) -> []; transfer_encoding_otp_6807(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/capital_transfer_encoding.html", @@ -2035,7 +2167,7 @@ empty_response_header_otp_6830(suite) -> []; empty_response_header_otp_6830(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html", {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL), @@ -2052,7 +2184,7 @@ no_content_204_otp_6982(suite) -> []; no_content_204_otp_6982(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html", {ok, {{_,204,_}, [], []}} = httpc:request(URL), @@ -2070,7 +2202,7 @@ missing_CR_otp_7304(suite) -> []; missing_CR_otp_7304(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html", {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL), @@ -2089,7 +2221,7 @@ otp_7883_1(suite) -> otp_7883_1(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", {error, socket_closed_remotely} = httpc:request(URL), @@ -2105,7 +2237,7 @@ otp_7883_2(suite) -> otp_7883_2(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", Method = get, @@ -2626,7 +2758,7 @@ otp_8371(suite) -> []; otp_8371(Config) when is_list(Config) -> ok = httpc:set_options([{ipv6, disabled}]), % also test the old option - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/ensure_host_header_with_port.html", @@ -2864,75 +2996,179 @@ receive_streamed_body(RequestId, Body, Pid) -> test_server:fail(Msg) end. +%% Perform a synchronous stop +dummy_server_stop(Pid) -> + Pid ! {stop, self()}, + receive + {stopped, Pid} -> + ok + end. + +dummy_server(IpV) -> + dummy_server(self(), ip_comm, IpV, []). +dummy_server(SocketType, IpV, Extra) -> + dummy_server(self(), SocketType, IpV, Extra). -dummy_server(Caller, IpV) -> - Pid = spawn(httpc_SUITE, dummy_server_init, [Caller, IpV]), +dummy_server(Caller, SocketType, IpV, Extra) -> + Args = [Caller, SocketType, IpV, Extra], + Pid = spawn(httpc_SUITE, dummy_server_init, Args), receive {port, Port} -> {Pid, Port} end. -dummy_server_init(Caller, IpV) -> +dummy_server_init(Caller, ip_comm, IpV, _) -> + BaseOpts = [binary, {packet, 0}, {reuseaddr,true}, {active, false}], {ok, ListenSocket} = case IpV of ipv4 -> - gen_tcp:listen(0, [binary, inet, {packet, 0}, - {reuseaddr,true}, - {active, false}]); + tsp("ip_comm ipv4 listen", []), + gen_tcp:listen(0, [inet | BaseOpts]); ipv6 -> - gen_tcp:listen(0, [binary, inet6, {packet, 0}, - {reuseaddr,true}, - {active, false}]) + tsp("ip_comm ipv6 listen", []), + gen_tcp:listen(0, [inet6 | BaseOpts]) end, {ok, Port} = inet:port(ListenSocket), - tsp("dummy_server_init -> Port: ~p", [Port]), + tsp("dummy_server_init(ip_comm) -> Port: ~p", [Port]), Caller ! {port, Port}, - dummy_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, - [], ListenSocket). + dummy_ipcomm_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket); +dummy_server_init(Caller, essl, IpV, SSLOptions) -> + BaseOpts = [{ssl_imp, new}, + {backlog, 128}, binary, {reuseaddr,true}, {active, false} | + SSLOptions], + dummy_ssl_server_init(Caller, BaseOpts, IpV); +dummy_server_init(Caller, ossl, IpV, SSLOptions) -> + BaseOpts = [{ssl_imp, old}, + {backlog, 128}, binary, {active, false} | SSLOptions], + dummy_ssl_server_init(Caller, BaseOpts, IpV). + +dummy_ssl_server_init(Caller, BaseOpts, IpV) -> + {ok, ListenSocket} = + case IpV of + ipv4 -> + tsp("dummy_ssl_server_init -> ssl ipv4 listen", []), + ssl:listen(0, [inet | BaseOpts]); + ipv6 -> + tsp("dummy_ssl_server_init -> ssl ipv6 listen", []), + ssl:listen(0, [inet6 | BaseOpts]) + end, + tsp("dummy_ssl_server_init -> ListenSocket: ~p", [ListenSocket]), + {ok, {_, Port}} = ssl:sockname(ListenSocket), + tsp("dummy_ssl_server_init -> Port: ~p", [Port]), + Caller ! {port, Port}, + dummy_ssl_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket). -dummy_server_loop(MFA, Handlers, ListenSocket) -> +dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) -> receive stop -> - lists:foreach(fun(Handler) -> Handler ! stop end, Handlers) + tsp("dummy_ipcomm_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ipcomm_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} after 0 -> + tsp("dummy_ipcomm_server_loop -> await accept", []), {ok, Socket} = gen_tcp:accept(ListenSocket), + tsp("dummy_ipcomm_server_loop -> accepted: ~p", [Socket]), HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_icomm_server_loop -> handler created: ~p", [HandlerPid]), gen_tcp:controlling_process(Socket, HandlerPid), - HandlerPid ! controller, - dummy_server_loop(MFA, [HandlerPid | Handlers], + tsp("dummy_ipcomm_server_loop -> " + "control transfered to handler", []), + HandlerPid ! ipcomm_controller, + tsp("dummy_ipcomm_server_loop -> " + "handler informed about control transfer", []), + dummy_ipcomm_server_loop(MFA, [HandlerPid | Handlers], ListenSocket) end. +dummy_ssl_server_loop(MFA, Handlers, ListenSocket) -> + receive + stop -> + tsp("dummy_ssl_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ssl_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} + after 0 -> + tsp("dummy_ssl_server_loop -> await accept", []), + {ok, Socket} = ssl:transport_accept(ListenSocket), + tsp("dummy_ssl_server_loop -> accepted: ~p", [Socket]), + HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_ssl_server_loop -> handler created: ~p", [HandlerPid]), + ssl:controlling_process(Socket, HandlerPid), + tsp("dummy_ssl_server_loop -> control transfered to handler", []), + HandlerPid ! ssl_controller, + tsp("dummy_ssl_server_loop -> " + "handler informed about control transfer", []), + dummy_ssl_server_loop(MFA, [HandlerPid | Handlers], + ListenSocket) + end. + dummy_request_handler(MFA, Socket) -> + tsp("spawn request handler", []), spawn(httpc_SUITE, dummy_request_handler_init, [MFA, Socket]). dummy_request_handler_init(MFA, Socket) -> - receive - controller -> - inet:setopts(Socket, [{active, true}]) - end, - dummy_request_handler_loop(MFA, Socket). + SockType = + receive + ipcomm_controller -> + tsp("dummy_request_handler_init -> " + "received ip_comm controller - activate", []), + inet:setopts(Socket, [{active, true}]), + ip_comm; + ssl_controller -> + tsp("dummy_request_handler_init -> " + "received ssl controller - activate", []), + ssl:setopts(Socket, [{active, true}]), + ssl + end, + dummy_request_handler_loop(MFA, SockType, Socket). -dummy_request_handler_loop({Module, Function, Args}, Socket) -> +dummy_request_handler_loop({Module, Function, Args}, SockType, Socket) -> tsp("dummy_request_handler_loop -> entry with" "~n Module: ~p" "~n Function: ~p" "~n Args: ~p", [Module, Function, Args]), receive - {tcp, _, Data} -> - tsp("dummy_request_handler_loop -> Data ~p", [Data]), - case handle_request(Module, Function, [Data | Args], Socket) of - stop -> + {Proto, _, Data} when (Proto =:= tcp) orelse (Proto =:= ssl) -> + tsp("dummy_request_handler_loop -> [~w] Data ~p", [Proto, Data]), + case handle_request(Module, Function, [Data | Args], Socket, Proto) of + stop when Proto =:= tcp -> gen_tcp:close(Socket); + stop when Proto =:= ssl -> + ssl:close(Socket); NewMFA -> - dummy_request_handler_loop(NewMFA, Socket) + dummy_request_handler_loop(NewMFA, SockType, Socket) end; - stop -> - gen_tcp:close(Socket) + stop when SockType =:= ip_comm -> + gen_tcp:close(Socket); + stop when SockType =:= ssl -> + ssl:close(Socket) end. -handle_request(Module, Function, Args, Socket) -> + +mk_close(tcp) -> fun(Sock) -> gen_tcp:close(Sock) end; +mk_close(ssl) -> fun(Sock) -> ssl:close(Sock) end. + +mk_send(tcp) -> fun(Sock, Data) -> gen_tcp:send(Sock, Data) end; +mk_send(ssl) -> fun(Sock, Data) -> ssl:send(Sock, Data) end. + +handle_request(Module, Function, Args, Socket, Proto) -> + Close = mk_close(Proto), + Send = mk_send(Proto), + handle_request(Module, Function, Args, Socket, Close, Send). + +handle_request(Module, Function, Args, Socket, Close, Send) -> tsp("handle_request -> entry with" "~n Module: ~p" "~n Function: ~p" @@ -2941,7 +3177,7 @@ handle_request(Module, Function, Args, Socket) -> {ok, Result} -> tsp("handle_request -> ok" "~n Result: ~p", [Result]), - case (catch handle_http_msg(Result, Socket)) of + case (catch handle_http_msg(Result, Socket, Close, Send)) of stop -> stop; <<>> -> @@ -2949,7 +3185,8 @@ handle_request(Module, Function, Args, Socket) -> {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]}; Data -> handle_request(httpd_request, parse, - [Data |[?HTTP_MAX_HEADER_SIZE]], Socket) + [Data |[?HTTP_MAX_HEADER_SIZE]], Socket, + Close, Send) end; NewMFA -> tsp("handle_request -> " @@ -2957,7 +3194,7 @@ handle_request(Module, Function, Args, Socket) -> NewMFA end. -handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> +handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket, Close, Send) -> tsp("handle_http_msg -> entry with: " "~n RelUri: ~p" "~n Headers: ~p" @@ -3114,16 +3351,16 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> "Expires:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++ "Proxy-Authenticate:#1Basic" ++ "\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")), + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/capital_transfer_encoding.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")), + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/cookie.html" -> "HTTP/1.1 200 ok\r\n" ++ @@ -3142,20 +3379,20 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> "/once_chunked.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/once.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Content-Length:32\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, "<HTML><BODY>fo"), + Send(Socket, Head), + Send(Socket, "<HTML><BODY>fo"), test_server:sleep(1000), - gen_tcp:send(Socket, "ob"), + Send(Socket, "ob"), test_server:sleep(1000), - gen_tcp:send(Socket, "ar</BODY></HTML>"); + Send(Socket, "ar</BODY></HTML>"); "/invalid_http.html" -> "HTTP/1.1 301\r\nDate:Sun, 09 Dec 2007 13:04:18 GMT\r\n" ++ "Transfer-Encoding:chunked\r\n\r\n"; @@ -3178,9 +3415,9 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> ok; close -> %% Nothing to send, just close - gen_tcp:close(Socket); + Close(Socket); _ when is_list(Msg) orelse is_binary(Msg) -> - gen_tcp:send(Socket, Msg) + Send(Socket, Msg) end, tsp("handle_http_msg -> done"), NextRequest. @@ -3316,3 +3553,20 @@ dummy_ssl_server_hang_loop(_) -> stop -> ok end. + + +ensure_started([]) -> + ok; +ensure_started([App|Apps]) -> + ensure_started(App), + ensure_started(Apps); +ensure_started(App) when is_atom(App) -> + case (catch application:start(App)) of + ok -> + ok; + {error, {already_started, _}} -> + ok; + Error -> + throw({error, {failed_starting, App, Error}}) + end. + diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index fde5178879..c4d4bf969b 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -207,8 +207,11 @@ -export([ticket_5775/1,ticket_5865/1,ticket_5913/1,ticket_6003/1, ticket_7304/1]). -%%% Misc --export([ipv6_hostname/1, ipv6_address/1]). +%%% IPv6 tests +-export([ipv6_hostname_ipcomm/0, ipv6_hostname_ipcomm/1, + ipv6_address_ipcomm/0, ipv6_address_ipcomm/1, + ipv6_hostname_essl/0, ipv6_hostname_essl/1, + ipv6_address_essl/0, ipv6_address_essl/1]). %% Help functions -export([cleanup_mnesia/0, setup_mnesia/0, setup_mnesia/1]). @@ -241,9 +244,15 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [{group, ip}, {group, ssl}, {group, http_1_1_ip}, - {group, http_1_0_ip}, {group, http_0_9_ip}, - {group, tickets}]. + [ + {group, ip}, + {group, ssl}, + {group, http_1_1_ip}, + {group, http_1_0_ip}, + {group, http_0_9_ip}, + {group, ipv6}, + {group, tickets} + ]. groups() -> [{ip, [], @@ -329,7 +338,8 @@ groups() -> {http_1_0_ip, [], [ip_head_1_0, ip_get_1_0, ip_post_1_0]}, {http_0_9_ip, [], [ip_get_0_9]}, - {ipv6, [], [ipv6_hostname, ipv6_address]}, + {ipv6, [], [ipv6_hostname_ipcomm, ipv6_address_ipcomm, + ipv6_hostname_essl, ipv6_address_essl]}, {tickets, [], [ticket_5775, ticket_5865, ticket_5913, ticket_6003, ticket_7304]}]. @@ -408,10 +418,10 @@ init_per_testcase2(Case, Config) -> "~n Config: ~p" "~n", [?MODULE, Case, Config]), - IpNormal = integer_to_list(?IP_PORT) ++ ".conf", - IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf", - SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", - SslHtacess = integer_to_list(?SSL_PORT) ++ "htacess.conf", + IpNormal = integer_to_list(?IP_PORT) ++ ".conf", + IpHtaccess = integer_to_list(?IP_PORT) ++ "htaccess.conf", + SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", + SslHtaccess = integer_to_list(?SSL_PORT) ++ "htaccess.conf", DataDir = ?config(data_dir, Config), SuiteTopDir = ?config(suite_top_dir, Config), @@ -471,9 +481,9 @@ init_per_testcase2(Case, Config) -> io:format(user, "~w:init_per_testcase2(~w) -> ip testcase setups~n", [?MODULE, Case]), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], - normal_acess, IpNormal), + normal_access, IpNormal), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], - mod_htaccess, IpHtacess), + mod_htaccess, IpHtaccess), %% To be used by SSL test cases io:format(user, "~w:init_per_testcase2(~w) -> ssl testcase setups~n", @@ -491,9 +501,9 @@ init_per_testcase2(Case, Config) -> end, create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], - normal_acess, SslNormal), + normal_access, SslNormal), create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], - mod_htaccess, SslHtacess), + mod_htaccess, SslHtaccess), %% To be used by IPv6 test cases. Case-clause is so that %% you can do ts:run(inets, httpd_SUITE, <test case>) @@ -501,22 +511,52 @@ init_per_testcase2(Case, Config) -> %% on 'test_host_ipv6_only' that will only be present %% when you run the whole test suite due to shortcomings %% of the test server. - %% case (catch ?config(test_host_ipv6_only, Config)) of - %% {_,IPv6Host,IPv6Adress,_,_} -> - %% create_ipv6_config([{port, ?IP_PORT}, - %% {sock_type, ip_comm} | NewConfig], - %% "ipv6_hostname.conf", IPv6Host), - %% create_ipv6_config([{port, ?IP_PORT}, - %% {sock_type, ip_comm} | NewConfig], - %% "ipv6_address.conf", IPv6Adress); - %% _ -> - %% ok - %% end, - + + io:format(user, "~w:init_per_testcase2(~w) -> " + "maybe generate IPv6 config file(s)", [?MODULE, Case]), + NewConfig2 = + case atom_to_list(Case) of + "ipv6_" ++ _ -> + case (catch inets_test_lib:has_ipv6_support(NewConfig)) of + {ok, IPv6Address0} -> + {ok, Hostname} = inet:gethostname(), + IPv6Address = http_transport:ipv6_name(IPv6Address0), + create_ipv6_config([{port, ?IP_PORT}, + {sock_type, ip_comm}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_hostname_ipcomm.conf", + Hostname), + create_ipv6_config([{port, ?IP_PORT}, + {sock_type, ip_comm}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_address_ipcomm.conf", + IPv6Address), + create_ipv6_config([{port, ?SSL_PORT}, + {sock_type, essl}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_hostname_essl.conf", + Hostname), + create_ipv6_config([{port, ?SSL_PORT}, + {sock_type, essl}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_address_essl.conf", + IPv6Address), + [{ipv6_host, IPv6Address} | NewConfig]; + _ -> + NewConfig + end; + _ -> + NewConfig + end, + io:format(user, "~w:init_per_testcase2(~w) -> done~n", [?MODULE, Case]), - NewConfig. + NewConfig2. init_per_testcase3(Case, Config) -> @@ -547,10 +587,10 @@ init_per_testcase3(Case, Config) -> [?MODULE, Case]), inets:disable_trace(); _ -> - %% TraceLevel = max, io:format(user, "~w:init_per_testcase3(~w) -> enabling trace", [?MODULE, Case]), - TraceLevel = 70, + %% TraceLevel = 70, + TraceLevel = max, TraceDest = io, inets:enable_trace(TraceLevel, TraceDest, httpd) end, @@ -569,7 +609,7 @@ init_per_testcase3(Case, Config) -> inets_test_lib:start_http_server( filename:join(TcTopDir, integer_to_list(?IP_PORT) ++ - "htacess.conf")), + "htaccess.conf")), "mod_htaccess"; "ip_" ++ Rest -> inets_test_lib:start_http_server( @@ -602,7 +642,7 @@ init_per_testcase3(Case, Config) -> case inets_test_lib:start_http_server_ssl( filename:join(TcTopDir, integer_to_list(?SSL_PORT) ++ - "htacess.conf"), SslTag) of + "htaccess.conf"), SslTag) of ok -> "mod_htaccess"; Other -> @@ -627,16 +667,13 @@ init_per_testcase3(Case, Config) -> {skip, "SSL does not seem to be supported"} end; "ipv6_" ++ _ = TestCaseStr -> - {ok, Hostname} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname), - ?config(ipv6_hosts, Config)) of - true -> + case inets_test_lib:has_ipv6_support() of + {ok, _} -> inets_test_lib:start_http_server( filename:join(TcTopDir, TestCaseStr ++ ".conf")); - false -> + _ -> {skip, "Host does not support IPv6"} end end, @@ -650,8 +687,8 @@ init_per_testcase3(Case, Config) -> "mod_htaccess" -> ServerRoot = ?config(server_root, Config), Path = filename:join([ServerRoot, "htdocs"]), - catch remove_htacess(Path), - create_htacess_data(Path, ?config(address, Config)), + catch remove_htaccess(Path), + create_htaccess_data(Path, ?config(address, Config)), [{watchdog, Dog} | NewConfig]; "range" -> ServerRoot = ?config(server_root, Config), @@ -2409,30 +2446,76 @@ ip_mod_cgi_chunked_encoding_test(Config) when is_list(Config) -> ok. %------------------------------------------------------------------------- -ipv6_hostname(doc) -> + +ipv6_hostname_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_hostname_ipcomm(X) -> + SocketType = ip_comm, + Port = ?IP_PORT, + ipv6_hostname(SocketType, Port, X). + +ipv6_hostname_essl() -> + [{require, ipv6_hosts}]. +ipv6_hostname_essl(X) -> + SocketType = essl, + Port = ?SSL_PORT, + ipv6_hostname(SocketType, Port, X). + +ipv6_hostname(_SocketType, _Port, doc) -> ["Test standard ipv6 address"]; -ipv6_hostname(suite)-> +ipv6_hostname(_SocketType, _Port, suite)-> []; -ipv6_hostname(Config) when is_list(Config) -> +ipv6_hostname(SocketType, Port, Config) when is_list(Config) -> + tsp("ipv6_hostname -> entry with" + "~n SocketType: ~p" + "~n Port: ~p" + "~n Config: ~p", [SocketType, Port, Config]), Host = ?config(host, Config), - httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, node(), - "GET / HTTP/1.1\r\n\r\n", - [{statuscode, 200}, - {version, "HTTP/1.1"}]), + URI = "GET HTTP://" ++ + Host ++ ":" ++ integer_to_list(Port) ++ "/ HTTP/1.1\r\n\r\n", + tsp("ipv6_hostname -> Host: ~p", [Host]), + httpd_test_lib:verify_request(SocketType, Host, Port, [inet6], + node(), + URI, + [{statuscode, 200}, {version, "HTTP/1.1"}]), ok. %%------------------------------------------------------------------------- -ipv6_address(doc) -> + +ipv6_address_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_address_ipcomm(X) -> + SocketType = ip_comm, + Port = ?IP_PORT, + ipv6_address(SocketType, Port, X). + +ipv6_address_essl() -> + [{require, ipv6_hosts}]. +ipv6_address_essl(X) -> + SocketType = essl, + Port = ?SSL_PORT, + ipv6_address(SocketType, Port, X). + +ipv6_address(_SocketType, _Port, doc) -> ["Test standard ipv6 address"]; -ipv6_address(suite)-> +ipv6_address(_SocketType, _Port, suite)-> []; -ipv6_address(Config) when is_list(Config) -> - httpd_test_lib:verify_request(ip_comm, ?IPV6_LOCAL_HOST, ?IP_PORT, - node(), "GET / HTTP/1.1\r\n\r\n", - [{statuscode, 200}, - {version, "HTTP/1.1"}]), +ipv6_address(SocketType, Port, Config) when is_list(Config) -> + tsp("ipv6_address -> entry with" + "~n SocketType: ~p" + "~n Port: ~p" + "~n Config: ~p", [SocketType, Port, Config]), + Host = ?config(host, Config), + tsp("ipv6_address -> Host: ~p", [Host]), + URI = "GET HTTP://" ++ + Host ++ ":" ++ integer_to_list(Port) ++ "/ HTTP/1.1\r\n\r\n", + httpd_test_lib:verify_request(SocketType, Host, Port, [inet6], + node(), + URI, + [{statuscode, 200}, {version, "HTTP/1.1"}]), ok. + %%-------------------------------------------------------------------- ticket_5775(doc) -> ["Tests that content-length is correct"]; @@ -2805,22 +2888,22 @@ cleanup_mnesia() -> mnesia:delete_schema([node()]), ok. -create_htacess_data(Path, IpAddress)-> - create_htacess_dirs(Path), +create_htaccess_data(Path, IpAddress)-> + create_htaccess_dirs(Path), create_html_file(filename:join([Path,"ht/open/dummy.html"])), create_html_file(filename:join([Path,"ht/blocknet/dummy.html"])), create_html_file(filename:join([Path,"ht/secret/dummy.html"])), create_html_file(filename:join([Path,"ht/secret/top_secret/dummy.html"])), - create_htacess_file(filename:join([Path,"ht/open/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/open/.htaccess"]), Path, "user one Aladdin"), - create_htacess_file(filename:join([Path,"ht/secret/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/secret/.htaccess"]), Path, "group group1 group2"), - create_htacess_file(filename:join([Path, + create_htaccess_file(filename:join([Path, "ht/secret/top_secret/.htaccess"]), Path, "user four"), - create_htacess_file(filename:join([Path,"ht/blocknet/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/blocknet/.htaccess"]), Path, nouser, IpAddress), create_user_group_file(filename:join([Path,"ht","users.file"]), @@ -2835,7 +2918,7 @@ create_html_file(PathAndFileName)-> "<html><head><title>test</title></head> <body>testar</body></html>")). -create_htacess_file(PathAndFileName, BaseDir, RequireData)-> +create_htaccess_file(PathAndFileName, BaseDir, RequireData)-> file:write_file(PathAndFileName, list_to_binary( "AuthUserFile "++ BaseDir ++ @@ -2844,7 +2927,7 @@ create_htacess_file(PathAndFileName, BaseDir, RequireData)-> " Basic\n<Limit>\nrequire " ++ RequireData ++ "\n</Limit>")). -create_htacess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> +create_htaccess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> file:write_file(PathAndFileName,list_to_binary( "AuthUserFile "++ BaseDir ++ "/ht/users.file\nAuthGroupFile " ++ @@ -2858,14 +2941,14 @@ create_htacess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> create_user_group_file(PathAndFileName, Data)-> file:write_file(PathAndFileName, list_to_binary(Data)). -create_htacess_dirs(Path)-> +create_htaccess_dirs(Path)-> ok = file:make_dir(filename:join([Path,"ht"])), ok = file:make_dir(filename:join([Path,"ht/open"])), ok = file:make_dir(filename:join([Path,"ht/blocknet"])), ok = file:make_dir(filename:join([Path,"ht/secret"])), ok = file:make_dir(filename:join([Path,"ht/secret/top_secret"])). -remove_htacess_dirs(Path)-> +remove_htaccess_dirs(Path)-> file:del_dir(filename:join([Path,"ht/secret/top_secret"])), file:del_dir(filename:join([Path,"ht/secret"])), file:del_dir(filename:join([Path,"ht/blocknet"])), @@ -2888,7 +2971,7 @@ format_ip(IpAddress,Pos)when Pos > 0-> format_ip(IpAddress, _Pos)-> "1" ++ IpAddress. -remove_htacess(Path)-> +remove_htaccess(Path)-> file:delete(filename:join([Path,"ht/open/dummy.html"])), file:delete(filename:join([Path,"ht/secret/dummy.html"])), file:delete(filename:join([Path,"ht/secret/top_secret/dummy.html"])), @@ -2899,7 +2982,7 @@ remove_htacess(Path)-> file:delete(filename:join([Path,"ht/secret/top_secret/.htaccess"])), file:delete(filename:join([Path,"ht","users.file"])), file:delete(filename:join([Path,"ht","groups.file"])), - remove_htacess_dirs(Path). + remove_htaccess_dirs(Path). dos_hostname_poll(Type, Host, Port, Node, Hosts) -> @@ -2939,35 +3022,66 @@ create_range_data(Path) -> "12345678901234567890", "12345678901234567890"])). -%% create_ipv6_config(Config, FileName, Ipv6Address) -> -%% ServerRoot = ?config(server_root, Config), -%% TcTopDir = ?config(tc_top_dir, Config), -%% Port = ?config(port, Config), -%% SockType = ?config(sock_type, Config), -%% -%% MaxHdrSz = io_lib:format("~p", [256]), -%% MaxHdrAct = io_lib:format("~p", [close]), -%% -%% Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" -%% " mod_include mod_dir mod_get mod_head" -%% " mod_log mod_disk_log mod_trace", -%% -%% HttpConfig = [cline(["BindAddress ", "[" ++ Ipv6Address ++"]|inet6"]), -%% cline(["Port ", integer_to_list(Port)]), -%% cline(["ServerName ", "httpc_test"]), -%% cline(["SocketType ", atom_to_list(SockType)]), -%% cline([Mod_order]), -%% cline(["ServerRoot ", ServerRoot]), -%% cline(["DocumentRoot ", -%% filename:join(ServerRoot, "htdocs")]), -%% cline(["MaxHeaderSize ",MaxHdrSz]), -%% cline(["MaxHeaderAction ",MaxHdrAct]), -%% cline(["DirectoryIndex ", "index.html "]), -%% cline(["DefaultType ", "text/plain"])], -%% ConfigFile = filename:join([TcTopDir,FileName]), -%% {ok, Fd} = file:open(ConfigFile, [write]), -%% ok = file:write(Fd, lists:flatten(HttpConfig)), -%% ok = file:close(Fd). +create_ipv6_config(Config, FileName, Ipv6Address) -> + ServerRoot = ?config(server_root, Config), + TcTopDir = ?config(tc_top_dir, Config), + Port = ?config(port, Config), + SockType = ?config(sock_type, Config), + Mods = io_lib:format("~p", [httpd_mod]), + Funcs = io_lib:format("~p", [ssl_password_cb]), + Host = ?config(ipv6_host, Config), + + MaxHdrSz = io_lib:format("~p", [256]), + MaxHdrAct = io_lib:format("~p", [close]), + + Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" + " mod_include mod_dir mod_get mod_head" + " mod_log mod_disk_log mod_trace", + + SSL = + if + (SockType =:= ssl) orelse + (SockType =:= ossl) orelse + (SockType =:= essl) -> + [cline(["SSLCertificateFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLCertificateKeyFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLCACertificateFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLPasswordCallbackModule ", Mods]), + cline(["SSLPasswordCallbackFunction ", Funcs]), + cline(["SSLVerifyClient 0"]), + cline(["SSLVerifyDepth 1"])]; + true -> + [] + end, + + BindAddress = "[" ++ Ipv6Address ++"]|inet6", + + HttpConfig = + [cline(["BindAddress ", BindAddress]), + cline(["Port ", integer_to_list(Port)]), + cline(["ServerName ", Host]), + cline(["SocketType ", atom_to_list(SockType)]), + cline([Mod_order]), + cline(["ServerRoot ", ServerRoot]), + cline(["DocumentRoot ", filename:join(ServerRoot, "htdocs")]), + cline(["MaxHeaderSize ",MaxHdrSz]), + cline(["MaxHeaderAction ",MaxHdrAct]), + cline(["DirectoryIndex ", "index.html "]), + cline(["DefaultType ", "text/plain"]), + SSL], + ConfigFile = filename:join([TcTopDir,FileName]), + {ok, Fd} = file:open(ConfigFile, [write]), + ok = file:write(Fd, lists:flatten(HttpConfig)), + ok = file:close(Fd). + + +%% tsp(F) -> +%% inets_test_lib:tsp(F). +tsp(F, A) -> + inets_test_lib:tsp(F, A). tsf(Reason) -> - test_server:fail(Reason). + inets_test_lib:tsf(Reason). diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index 3189a758a5..2903aaafa5 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -22,7 +22,7 @@ -include("inets_test_lib.hrl"). %% Poll functions --export([verify_request/6, verify_request/7, is_expect/1]). +-export([verify_request/6, verify_request/7, verify_request/8, is_expect/1]). -record(state, {request, % string() socket, % socket() @@ -81,33 +81,57 @@ %%------------------------------------------------------------------ verify_request(SocketType, Host, Port, Node, RequestStr, Options) -> verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000). -verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) -> - {ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port), +verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options) + when is_list(TranspOpts) -> + verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, 30000); +verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) + when (is_integer(TimeOut) orelse (TimeOut =:= infinity)) -> + verify_request(SocketType, Host, Port, [], Node, RequestStr, Options, TimeOut). +verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, TimeOut) -> + tsp("verify_request -> entry with" + "~n SocketType: ~p" + "~n Host: ~p" + "~n Port: ~p" + "~n TranspOpts: ~p" + "~n Node: ~p" + "~n Options: ~p" + "~n TimeOut: ~p", + [SocketType, Host, Port, TranspOpts, Node, Options, TimeOut]), + case (catch inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts)) of + {ok, Socket} -> + tsp("verify_request -> connected - now send message"), + SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), + tsp("verify_request -> send result: " + "~n ~p", [SendRes]), + State = case inets_regexp:match(RequestStr, "printenv") of + nomatch -> + #state{}; + _ -> + #state{print = true} + end, + + case request(State#state{request = RequestStr, + socket = Socket}, TimeOut) of + {error, Reason} -> + tsp("request failed: " + "~n Reason: ~p", [Reason]), + {error, Reason}; + NewState -> + tsp("validate reply: " + "~n NewState: ~p", [NewState]), + ValidateResult = + validate(RequestStr, NewState, Options, Node, Port), + tsp("validation result: " + "~n ~p", [ValidateResult]), + inets_test_lib:close(SocketType, Socket), + ValidateResult + end; - _SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), - - State = case inets_regexp:match(RequestStr, "printenv") of - nomatch -> - #state{}; - _ -> - #state{print = true} - end, - - case request(State#state{request = RequestStr, - socket = Socket}, TimeOut) of - {error, Reason} -> - tsp("request failed: " - "~n Reason: ~p", [Reason]), - {error, Reason}; - NewState -> - tsp("validate reply: " - "~n NewState: ~p", [NewState]), - ValidateResult = validate(RequestStr, NewState, Options, - Node, Port), - tsp("validation result: " - "~n ~p", [ValidateResult]), - inets_test_lib:close(SocketType, Socket), - ValidateResult + ConnectError -> + tsp("verify_request -> connect failed: " + "~n ~p" + "~n", [ConnectError]), + tsf({connect_failure, ConnectError}) end. request(#state{mfa = {Module, Function, Args}, @@ -214,7 +238,10 @@ validate(RequestStr, #state{status_line = {Version, StatusCode, _}, headers = Headers, body = Body}, Options, N, P) -> - %io:format("Status~p: H:~p B:~p~n", [StatusCode, Headers, Body]), + %% tsp("validate -> entry with" + %% "~n StatusCode: ~p" + %% "~n Headers: ~p" + %% "~n Body: ~p", [StatusCode, Headers, Body]), check_version(Version, Options), case lists:keysearch(statuscode, 1, Options) of {value, _} -> @@ -342,8 +369,10 @@ print(_, _, #state{print = false}) -> ok. -%% tsp(F) -> -%% tsp(F, []). +tsp(F) -> + inets_test_lib:tsp(F). tsp(F, A) -> - test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + inets_test_lib:tsp(F, A). +tsf(Reason) -> + inets_test_lib:tsf(Reason). diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 6cedaf9638..2e19c41f16 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -26,18 +26,64 @@ -export([start_http_server/1, start_http_server/2]). -export([start_http_server_ssl/1, start_http_server_ssl/2]). -export([hostname/0]). --export([connect_bin/3, connect_byte/3, send/3, close/2]). +-export([connect_bin/3, connect_bin/4, + connect_byte/3, connect_byte/4, + send/3, close/2]). -export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]). -export([info/4, log/4, debug/4, print/4]). +-export([tsp/1, tsp/2, tsf/1]). -export([check_body/1]). -export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]). --export([oscmd/1]). +-export([oscmd/1, has_ipv6_support/1]). -export([non_pc_tc_maybe_skip/4, os_based_skip/1, skip/3, fail/3]). -export([flush/0]). -export([start_node/1, stop_node/1]). %% -- Misc os command and stuff +has_ipv6_support(Config) -> + case lists:keysearch(ipv6_hosts, 1, Config) of + false -> + %% Do a basic check to se if + %% our own host has a working IPv6 address... + tsp("has_ipv6_support -> no ipv6_hosts config"), + {ok, Hostname} = inet:gethostname(), + case inet:getaddrs(Hostname, inet6) of + {ok, [Addr|_]} when is_tuple(Addr) andalso + (element(1, Addr) =/= 0) -> + %% We actually need to test that the addr can be used, + %% this is done by attempting to create a (tcp) + %% listen socket + tsp("has_ipv6_support -> check Addr: ~p", [Addr]), + case (catch gen_tcp:listen(0, [inet6, {ip, Addr}])) of + {ok, LSock} -> + tsp("has_ipv6_support -> we are ipv6 host"), + gen_tcp:close(LSock), + {ok, Addr}; + _ -> + undefined + end; + _ -> + undefined + end; + {value, {_, Hosts}} when is_list(Hosts) -> + %% Check if our host is in the list of *known* IPv6 hosts + tsp("has_ipv6_support -> Hosts: ~p", [Hosts]), + {ok, Hostname} = inet:gethostname(), + case lists:member(list_to_atom(Hostname), Hosts) of + true -> + tsp("has_ipv6_support -> we are known ipv6 host"), + {ok, [Addr|_]} = inet:getaddrs(Hostname, inet6), + {ok, Addr}; + false -> + undefined + end; + + _ -> + undefined + + end. + oscmd(Cmd) -> string:strip(os:cmd(Cmd), right, $\n). @@ -87,31 +133,34 @@ start_http_server(Conf) -> start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND). start_http_server(Conf, essl = _SslTag) -> + tsp("start_http_server(essl) -> entry - try start crypto and public_key"), application:start(crypto), + application:start(public_key), do_start_http_server(Conf); -start_http_server(Conf, _SslTag) -> +start_http_server(Conf, SslTag) -> + tsp("start_http_server(~w) -> entry", [SslTag]), do_start_http_server(Conf). do_start_http_server(Conf) -> - tsp("start http server with " + tsp("do_start_http_server -> entry with" "~n Conf: ~p" "~n", [Conf]), application:load(inets), case application:set_env(inets, services, [{httpd, Conf}]) of ok -> + tsp("start_http_server -> httpd conf stored in inets app env"), case application:start(inets) of ok -> + tsp("start_http_server -> inets started"), ok; Error1 -> - test_server:format("<ERROR> Failed starting application: " - "~n Error: ~p" - "~n", [Error1]), + tsp("<ERROR> Failed starting application: " + "~n Error1: ~p", [Error1]), Error1 end; Error2 -> - test_server:format("<ERROR> Failed set application env: " - "~n Error: ~p" - "~n", [Error2]), + tsp("<ERROR> Failed set application env: " + "~n Error: ~p", [Error2]), Error2 end. @@ -285,29 +334,45 @@ os_based_skip(_) -> %% Host -> atom() | string() | {A, B, C, D} %% Port -> integer() -connect_bin(ssl, Host, Port) -> - connect(ssl, Host, Port, [binary, {packet,0}]); -connect_bin(ossl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, old}, binary, {packet,0}]); -connect_bin(essl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true}]); -connect_bin(ip_comm, Host, Port) -> - Opts = [inet6, binary, {packet,0}], +connect_bin(SockType, Host, Port) -> + connect_bin(SockType, Host, Port, []). + +connect_bin(ssl, Host, Port, Opts0) -> + Opts = [binary, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(ossl, Host, Port, Opts0) -> + Opts = [{ssl_imp, old}, binary, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(ip_comm, Host, Port, Opts0) -> + Opts = [binary, {packet, 0} | Opts0], connect(ip_comm, Host, Port, Opts). + +connect_byte(SockType, Host, Port) -> + connect_byte(SockType, Host, Port, []). -connect_byte(ssl, Host, Port) -> - connect(ssl, Host, Port, [{packet,0}]); -connect_byte(ossl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, old}, {packet,0}]); -connect_byte(essl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, new}, {packet,0}]); -connect_byte(ip_comm, Host, Port) -> - Opts = [inet6, {packet,0}], +connect_byte(ssl, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(ossl, Host, Port, Opts0) -> + Opts = [{ssl_imp, old}, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(ip_comm, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], connect(ip_comm, Host, Port, Opts). connect(ssl, Host, Port, Opts) -> + tsp("connect(ssl) -> entry with" + "~n Host: ~p" + "~n Port: ~p" + "~n Opts: ~p", [Host, Port, Opts]), ssl:start(), %% Does not support ipv6 in old ssl case ssl:connect(Host, Port, Opts) of @@ -319,21 +384,28 @@ connect(ssl, Host, Port, Opts) -> Error end; connect(ip_comm, Host, Port, Opts) -> + tsp("connect(ip_comm) -> entry with" + "~n Host: ~p" + "~n Port: ~p" + "~n Opts: ~p", [Host, Port, Opts]), case gen_tcp:connect(Host,Port, Opts) of {ok, Socket} -> - %% tsp("connect success"), + tsp("connect success"), {ok, Socket}; {error, nxdomain} -> - tsp("nxdomain opts: ~p", [Opts]), + tsp("connect error nxdomain when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, eafnosupport} -> - tsp("eafnosupport opts: ~p", [Opts]), + tsp("connect error eafnosupport when opts: ~p", [Opts]), + connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); + {error, econnreset} -> + tsp("connect error econnreset when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, enetunreach} -> - tsp("eafnosupport opts: ~p", [Opts]), + tsp("connect error eafnosupport when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, {enfile,_}} -> - tsp("Error enfile"), + tsp("connect error enfile when opts: ~p", [Opts]), {error, enfile}; Error -> tsp("Unexpected error: " @@ -414,7 +486,22 @@ flush() -> tsp(F) -> tsp(F, []). tsp(F, A) -> - test_server:format("~p ~p ~p:" ++ F ++ "~n", [node(), self(), ?MODULE | A]). + Timestamp = formated_timestamp(), + test_server:format("*** ~s ~p ~p ~w:" ++ F ++ "~n", + [Timestamp, node(), self(), ?MODULE | A]). tsf(Reason) -> test_server:fail(Reason). + +formated_timestamp() -> + format_timestamp( os:timestamp() ). + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index c0e25a30e3..4abc1733d3 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.6 +INETS_VSN = 5.7 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile index de10e31d36..214e994889 100644 --- a/lib/kernel/doc/src/Makefile +++ b/lib/kernel/doc/src/Makefile @@ -104,6 +104,8 @@ TOP_SPECS_FILE = specs.xml # ---------------------------------------------------- XML_FLAGS += +SPECS_ESRC = ../../src + SPECS_FLAGS = -I../../include # ---------------------------------------------------- diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml index 6f85388c22..98cdd416b0 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -288,196 +288,156 @@ <datatypes> <datatype> + <name name="load_ret"/> + </datatype> + <datatype> <name name="load_error_rsn"/> </datatype> </datatypes> <funcs> <func> - <name>set_path(Path) -> true | {error, What}</name> + <name name="set_path" arity="1"/> <fsummary>Set the code server search path</fsummary> - <type> - <v>Path = [Dir]</v> - <v>Dir = string()</v> - <v>What = bad_directory | bad_path</v> - </type> <desc> - <p>Sets the code path to the list of directories <c>Path</c>.</p> + <p>Sets the code path to the list of directories <c><anno>Path</anno></c>.</p> <p>Returns <c>true</c> if successful, or - <c>{error, bad_directory}</c> if any <c>Dir</c> is not + <c>{error, bad_directory}</c> if any <c><anno>Dir</anno></c> is not the name of a directory, or <c>{error, bad_path}</c> if the argument is invalid.</p> </desc> </func> <func> - <name>get_path() -> Path</name> + <name name="get_path" arity="0"/> <fsummary>Return the code server search path</fsummary> - <type> - <v>Path = [Dir]</v> - <v>Dir = string()</v> - </type> <desc> <p>Returns the code path</p> </desc> </func> <func> - <name>add_path(Dir) -> true | {error, What}</name> - <name>add_pathz(Dir) -> true | {error, What}</name> + <name name="add_path" arity="1"/> + <name name="add_pathz" arity="1"/> <fsummary>Add a directory to the end of the code path</fsummary> - <type> - <v>Dir = string()</v> - <v>What = bad_directory</v> - </type> + <type name="add_path_ret"/> <desc> - <p>Adds <c>Dir</c> to the code path. The directory is added as - the last directory in the new path. If <c>Dir</c> already + <p>Adds <c><anno>Dir</anno></c> to the code path. The directory is added as + the last directory in the new path. If <c><anno>Dir</anno></c> already exists in the path, it is not added.</p> <p>Returns <c>true</c> if successful, or - <c>{error, bad_directory}</c> if <c>Dir</c> is not the name + <c>{error, bad_directory}</c> if <c><anno>Dir</anno></c> is not the name of a directory.</p> </desc> </func> <func> - <name>add_patha(Dir) -> true | {error, What}</name> + <name name="add_patha" arity="1"/> <fsummary>Add a directory to the beginning of the code path</fsummary> - <type> - <v>Dir = string()</v> - <v>What = bad_directory</v> - </type> + <type name="add_path_ret"/> <desc> - <p>Adds <c>Dir</c> to the beginning of the code path. If - <c>Dir</c> already exists, it is removed from the old + <p>Adds <c><anno>Dir</anno></c> to the beginning of the code path. If + <c><anno>Dir</anno></c> already exists, it is removed from the old position in the code path.</p> <p>Returns <c>true</c> if successful, or - <c>{error, bad_directory}</c> if <c>Dir</c> is not the name + <c>{error, bad_directory}</c> if <c><anno>Dir</anno></c> is not the name of a directory.</p> </desc> </func> <func> - <name>add_paths(Dirs) -> ok</name> - <name>add_pathsz(Dirs) -> ok</name> + <name name="add_paths" arity="1"/> + <name name="add_pathsz" arity="1"/> <fsummary>Add directories to the end of the code path</fsummary> - <type> - <v>Dirs = [Dir]</v> - <v>Dir = string()</v> - </type> <desc> - <p>Adds the directories in <c>Dirs</c> to the end of the code - path. If a <c>Dir</c> already exists, it is not added. This + <p>Adds the directories in <c><anno>Dirs</anno></c> to the end of the code + path. If a <c><anno>Dir</anno></c> already exists, it is not added. This function always returns <c>ok</c>, regardless of the validity - of each individual <c>Dir</c>.</p> + of each individual <c><anno>Dir</anno></c>.</p> </desc> </func> <func> - <name>add_pathsa(Dirs) -> ok</name> + <name name="add_pathsa" arity="1"/> <fsummary>Add directories to the beginning of the code path</fsummary> - <type> - <v>Dirs = [Dir]</v> - <v>Dir = string()</v> - </type> <desc> - <p>Adds the directories in <c>Dirs</c> to the beginning of - the code path. If a <c>Dir</c> already exists, it is removed + <p>Adds the directories in <c><anno>Dirs</anno></c> to the beginning of + the code path. If a <c><anno>Dir</anno></c> already exists, it is removed from the old position in the code path. This function always returns <c>ok</c>, regardless of the validity of each - individual <c>Dir</c>.</p> + individual <c><anno>Dir</anno></c>.</p> </desc> </func> <func> - <name>del_path(Name | Dir) -> true | false | {error, What}</name> + <name name="del_path" arity="1"/> <fsummary>Delete a directory from the code path</fsummary> - <type> - <v>Name = atom()</v> - <v>Dir = string()</v> - <v>What = bad_name</v> - </type> <desc> <p>Deletes a directory from the code path. The argument can be - an atom <c>Name</c>, in which case the directory with - the name <c>.../Name[-Vsn][/ebin]</c> is deleted from the code + an atom <c><anno>Name</anno></c>, in which case the directory with + the name <c>.../<anno>Name</anno>[-Vsn][/ebin]</c> is deleted from the code path. It is also possible to give the complete directory name - <c>Dir</c> as argument.</p> + <c><anno>Dir</anno></c> as argument.</p> <p>Returns <c>true</c> if successful, or <c>false</c> if the directory is not found, or <c>{error, bad_name}</c> if the argument is invalid.</p> </desc> </func> <func> - <name>replace_path(Name, Dir) -> true | {error, What}</name> + <name name="replace_path" arity="2"/> <fsummary>Replace a directory with another in the code path</fsummary> - <type> - <v>Name = atom()</v> - <v>Dir = string()</v> - <v>What = bad_name | bad_directory | {badarg, term()}</v> - </type> <desc> <p>This function replaces an old occurrence of a directory - named <c>.../Name[-Vsn][/ebin]</c>, in the code path, with - <c>Dir</c>. If <c>Name</c> does not exist, it adds the new - directory <c>Dir</c> last in the code path. The new directory - must also be named <c>.../Name[-Vsn][/ebin]</c>. This function + named <c>.../<anno>Name</anno>[-Vsn][/ebin]</c>, in the code path, with + <c><anno>Dir</anno></c>. If <c><anno>Name</anno></c> does not exist, it adds the new + directory <c><anno>Dir</anno></c> last in the code path. The new directory + must also be named <c>.../<anno>Name</anno>[-Vsn][/ebin]</c>. This function should be used if a new version of the directory (library) is added to a running system.</p> <p>Returns <c>true</c> if successful, or - <c>{error, bad_name}</c> if <c>Name</c> is not found, or - <c>{error, bad_directory}</c> if <c>Dir</c> does not exist, or - <c>{error, {badarg, [Name, Dir]}}</c> if <c>Name</c> or - <c>Dir</c> is invalid.</p> + <c>{error, bad_name}</c> if <c><anno>Name</anno></c> is not found, or + <c>{error, bad_directory}</c> if <c><anno>Dir</anno></c> does not exist, or + <c>{error, {badarg, [<anno>Name</anno>, <anno>Dir</anno>]}}</c> if <c><anno>Name</anno></c> or + <c><anno>Dir</anno></c> is invalid.</p> </desc> </func> <func> - <name>load_file(Module) -> {module, Module} | {error, What}</name> + <name name="load_file" arity="1"/> <fsummary>Load a module</fsummary> - <type> - <v>Module = atom()</v> - <v>What = nofile | sticky_directory | badarg | term()</v> - </type> + <type name="load_ret"/> <desc> - <p>Tries to load the Erlang module <c>Module</c>, using + <p>Tries to load the Erlang module <c><anno>Module</anno></c>, using the code path. It looks for the object code file with an extension that corresponds to the Erlang machine used, for - example <c>Module.beam</c>. The loading fails if the module + example <c><anno>Module</anno>.beam</c>. The loading fails if the module name found in the object code differs from the name - <c>Module</c>. + <c><anno>Module</anno></c>. <seealso marker="#load_binary/3">load_binary/3</seealso> must be used to load object code with a module name that is different from the file name.</p> - <p>Returns <c>{module, Module}</c> if successful, or + <p>Returns <c>{module, <anno>Module</anno>}</c> if successful, or <c>{error, nofile}</c> if no object code is found, or <c>{error, sticky_directory}</c> if the object code resides in - a sticky directory, or <c>{error, badarg}</c> if the argument - is invalid. Also if the loading fails, an error tuple is + a sticky directory. Also if the loading fails, an error tuple is returned. See <seealso marker="erts:erlang#load_module/2">erlang:load_module/2</seealso> - for possible values of <c>What</c>.</p> + for possible values of <c><anno>What</anno></c>.</p> </desc> </func> <func> - <name>load_abs(Filename) -> {module, Module} | {error, What}</name> + <name name="load_abs" arity="1"/> <fsummary>Load a module, residing in a given file</fsummary> - <type> - <v>Filename = string()</v> - <v>Module = atom()</v> - <v>What = nofile | sticky_directory | badarg | term()</v> - </type> + <type name="load_ret"/> + <type name="loaded_filename"/> + <type name="loaded_ret_atoms"/> <desc> - <p>Does the same as <c>load_file(Module)</c>, but - <c>Filename</c> is either an absolute file name, or a + <p>Does the same as <c>load_file(<anno>Module</anno>)</c>, but + <c><anno>Filename</anno></c> is either an absolute file name, or a relative file name. The code path is not searched. It returns a value in the same way as <seealso marker="#load_file/1">load_file/1</seealso>. Note - that <c>Filename</c> should not contain the extension (for + that <c><anno>Filename</anno></c> should not contain the extension (for example <c>".beam"</c>); <c>load_abs/1</c> adds the correct extension itself.</p> </desc> </func> <func> - <name>ensure_loaded(Module) -> {module, Module} | {error, What}</name> + <name name="ensure_loaded" arity="1"/> <fsummary>Ensure that a module is loaded</fsummary> - <type> - <v>Module = atom()</v> - <v>What = nofile | sticky_directory | embedded | badarg | term()</v> - </type> <desc> <p>Tries to to load a module in the same way as <seealso marker="#load_file/1">load_file/1</seealso>, @@ -487,54 +447,45 @@ </desc> </func> <func> - <name>load_binary(Module, Filename, Binary) -> {module, Module} | {error, What}</name> + <name name="load_binary" arity="3"/> <fsummary>Load object code for a module</fsummary> - <type> - <v>Module = atom()</v> - <v>Filename = string()</v> - <v>What = sticky_directory | badarg | term()</v> - </type> + <type name="loaded_filename"/> + <type name="loaded_ret_atoms"/> <desc> <p>This function can be used to load object code on remote - Erlang nodes. The argument <c>Binary</c> must contain - object code for <c>Module</c>. - <c>Filename</c> is only used by the code server to keep a - record of from which file the object code for <c>Module</c> - comes. Accordingly, <c>Filename</c> is not opened and read by + Erlang nodes. The argument <c><anno>Binary</anno></c> must contain + object code for <c><anno>Module</anno></c>. + <c><anno>Filename</anno></c> is only used by the code server to keep a + record of from which file the object code for <c><anno>Module</anno></c> + comes. Accordingly, <c><anno>Filename</anno></c> is not opened and read by the code server.</p> - <p>Returns <c>{module, Module}</c> if successful, or + <p>Returns <c>{module, <anno>Module</anno>}</c> if successful, or <c>{error, sticky_directory}</c> if the object code resides in a sticky directory, or <c>{error, badarg}</c> if any argument is invalid. Also if the loading fails, an error tuple is returned. See <seealso marker="erts:erlang#load_module/2">erlang:load_module/2</seealso> - for possible values of <c>What</c>.</p> + for possible values of <c><anno>What</anno></c>.</p> </desc> </func> <func> - <name>delete(Module) -> true | false</name> + <name name="delete" arity="1"/> <fsummary>Removes current code for a module</fsummary> - <type> - <v>Module = atom()</v> - </type> <desc> - <p>Removes the current code for <c>Module</c>, that is, - the current code for <c>Module</c> is made old. This means + <p>Removes the current code for <c><anno>Module</anno></c>, that is, + the current code for <c><anno>Module</anno></c> is made old. This means that processes can continue to execute the code in the module, but that no external function calls can be made to it.</p> <p>Returns <c>true</c> if successful, or <c>false</c> if there - is old code for <c>Module</c> which must be purged first, or - if <c>Module</c> is not a (loaded) module.</p> + is old code for <c><anno>Module</anno></c> which must be purged first, or + if <c><anno>Module</anno></c> is not a (loaded) module.</p> </desc> </func> <func> - <name>purge(Module) -> true | false</name> + <name name="purge" arity="1"/> <fsummary>Removes old code for a module</fsummary> - <type> - <v>Module = atom()</v> - </type> <desc> - <p>Purges the code for <c>Module</c>, that is, removes code + <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code marked as old. If some processes still linger in the old code, these processes are killed before the code is removed.</p> <p>Returns <c>true</c> if successful and any process needed to @@ -542,31 +493,26 @@ </desc> </func> <func> - <name>soft_purge(Module) -> true | false</name> + <name name="soft_purge" arity="1"/> <fsummary>Removes old code for a module, unless no process uses it</fsummary> - <type> - <v>Module = atom()</v> - </type> <desc> - <p>Purges the code for <c>Module</c>, that is, removes code + <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code marked as old, but only if no processes linger in it.</p> <p>Returns <c>false</c> if the module could not be purged due to processes lingering in old code, otherwise <c>true</c>.</p> </desc> </func> <func> - <name>is_loaded(Module) -> {file, Loaded} | false</name> + <name name="is_loaded" arity="1"/> <fsummary>Check if a module is loaded</fsummary> - <type> - <v>Module = atom()</v> - <v>Loaded = Absname | preloaded | cover_compiled</v> - <v>Absname = string()</v> - </type> - <desc> - <p>Checks if <c>Module</c> is loaded. If it is, - <c>{file, Loaded}</c> is returned, otherwise <c>false</c>.</p> - <p>Normally, <c>Loaded</c> is the absolute file name - <c>Absname</c> from which the code was obtained. If the module + <type name="loaded_filename"/> + <type name="loaded_ret_atoms"/> + <type_desc name="loaded_filename"><c><anno>Filename</anno></c> is an absolute filename</type_desc> + <desc> + <p>Checks if <c><anno>Module</anno></c> is loaded. If it is, + <c>{file, <anno>Loaded</anno>}</c> is returned, otherwise <c>false</c>.</p> + <p>Normally, <c><anno>Loaded</anno></c> is the absolute file name + <c>Filename</c> from which the code was obtained. If the module is preloaded (see <seealso marker="sasl:script">script(4)</seealso>), <c>Loaded==preloaded</c>. If the module is Cover compiled (see @@ -575,32 +521,26 @@ </desc> </func> <func> - <name>all_loaded() -> [{Module, Loaded}]</name> + <name name="all_loaded" arity="0"/> <fsummary>Get all loaded modules</fsummary> - <type> - <v>Module = atom()</v> - <v>Loaded = Absname | preloaded | cover_compiled</v> - <v>Absname = string()</v> - </type> + <type name="loaded_filename"/> + <type name="loaded_ret_atoms"/> + <type_desc name="loaded_filename"><c><anno>Filename</anno></c> is an absolute filename</type_desc> <desc> - <p>Returns a list of tuples <c>{Module, Loaded}</c> for all - loaded modules. <c>Loaded</c> is normally the absolute file + <p>Returns a list of tuples <c>{<anno>Module</anno>, <anno>Loaded</anno>}</c> for all + loaded modules. <c><anno>Loaded</anno></c> is normally the absolute file name, as described for <seealso marker="#is_loaded/1">is_loaded/1</seealso>.</p> </desc> </func> <func> - <name>which(Module) -> Which</name> + <name name="which" arity="1"/> <fsummary>The object code file of a module</fsummary> - <type> - <v>Module = atom()</v> - <v>Which = Filename | non_existing | preloaded | cover_compiled</v> - <v>Filename = string()</v> - </type> + <type name="loaded_ret_atoms"/> <desc> <p>If the module is not loaded, this function searches the code path for the first file which contains object code for - <c>Module</c> and returns the absolute file name. If + <c><anno>Module</anno></c> and returns the absolute file name. If the module is loaded, it returns the name of the file which contained the loaded object code. If the module is pre-loaded, <c>preloaded</c> is returned. If the module is Cover compiled, @@ -609,21 +549,16 @@ </desc> </func> <func> - <name>get_object_code(Module) -> {Module, Binary, Filename} | error</name> + <name name="get_object_code" arity="1"/> <fsummary>Get the object code for a module</fsummary> - <type> - <v>Module = atom()</v> - <v>Binary = binary()</v> - <v>Filename = string()</v> - </type> <desc> <p>Searches the code path for the object code of the module - <c>Module</c>. It returns <c>{Module, Binary, Filename}</c> - if successful, and <c>error</c> if not. <c>Binary</c> is a + <c><anno>Module</anno></c>. It returns <c>{<anno>Module</anno>, <anno>Binary</anno>, <anno>Filename</anno>}</c> + if successful, and <c>error</c> if not. <c><anno>Binary</anno></c> is a binary data object which contains the object code for the module. This can be useful if code is to be loaded on a remote node in a distributed system. For example, loading - module <c>Module</c> on a node <c>Node</c> is done as + module <c><anno>Module</anno></c> on a node <c>Node</c> is done as follows:</p> <code type="none"> ... @@ -633,7 +568,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>root_dir() -> string()</name> + <name name="root_dir" arity="0"/> <fsummary>Root directory of Erlang/OTP</fsummary> <desc> <p>Returns the root directory of Erlang/OTP, which is @@ -644,7 +579,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>lib_dir() -> string()</name> + <name name="lib_dir" arity="0"/> <fsummary>Library directory of Erlang/OTP</fsummary> <desc> <p>Returns the library directory, <c>$OTPROOT/lib</c>, where @@ -655,19 +590,16 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>lib_dir(Name) -> string() | {error, bad_name}</name> + <name name="lib_dir" arity="1"/> <fsummary>Library directory for an application</fsummary> - <type> - <v>Name = atom()</v> - </type> <desc> <p>This function is mainly intended for finding out the path for the "library directory", the top directory, for an - application <c>Name</c> located under <c>$OTPROOT/lib</c> or + application <c><anno>Name</anno></c> located under <c>$OTPROOT/lib</c> or on a directory referred to via the <c>ERL_LIBS</c> environment variable.</p> - <p>If there is a regular directory called <c>Name</c> or - <c>Name-Vsn</c> in the code path with an <c>ebin</c> + <p>If there is a regular directory called <c><anno>Name</anno></c> or + <c><anno>Name</anno>-Vsn</c> in the code path with an <c>ebin</c> subdirectory, the path to this directory is returned (not the <c>ebin</c> directory). If the directory refers to a directory in an archive, the archive name is stripped away @@ -681,23 +613,19 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), <pre> > <input>code:lib_dir(mnesia).</input> "/usr/local/otp/lib/mnesia-4.2.2"</pre> - <p>Returns <c>{error, bad_name}</c> if <c>Name</c> + <p>Returns <c>{error, bad_name}</c> if <c><anno>Name</anno></c> is not the name of an application under <c>$OTPROOT/lib</c> or on a directory referred to via the <c>ERL_LIBS</c> environment variable. Fails with an exception if <c>Name</c> has the wrong type.</p> - <warning><p>For backward compatibility, <c>Name</c> is also allowed to + <warning><p>For backward compatibility, <c><anno>Name</anno></c> is also allowed to be a string. That will probably change in a future release.</p></warning> </desc> </func> <func> - <name>lib_dir(Name, SubDir) -> string() | {error, bad_name}</name> + <name name="lib_dir" arity="2"/> <fsummary>subdirectory for an application</fsummary> - <type> - <v>Name = atom()</v> - <v>SubDir = atom()</v> - </type> <desc> <p>Returns the path to a subdirectory directly under the top directory of an application. Normally the subdirectories @@ -711,12 +639,12 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), > <input>code:lib_dir(megaco, priv).</input> "/usr/local/otp/lib/megaco-3.9.1.1/priv"</pre> - <p>Fails with an exception if <c>Name</c> or <c>SubDir</c> has + <p>Fails with an exception if <c><anno>Name</anno></c> or <c><anno>SubDir</anno></c> has the wrong type.</p> </desc> </func> <func> - <name>compiler_dir() -> string()</name> + <name name="compiler_dir" arity="0"/> <fsummary>Library directory for the compiler</fsummary> <desc> <p>Returns the compiler library directory. Equivalent to @@ -724,21 +652,18 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>priv_dir(Name) -> string() | {error, bad_name}</name> + <name name="priv_dir" arity="1"/> <fsummary>Priv directory for an application</fsummary> - <type> - <v>Name = atom()</v> - </type> <desc> <p>Returns the path to the <c>priv</c> directory in an - application. Equivalent to <c>code:lib_dir(Name,priv).</c>.</p> + application. Equivalent to <c>code:lib_dir(<anno>Name</anno>, priv).</c>.</p> - <warning><p>For backward compatibility, <c>Name</c> is also allowed to + <warning><p>For backward compatibility, <c><anno>Name</anno></c> is also allowed to be a string. That will probably change in a future release.</p></warning> </desc> </func> <func> - <name>objfile_extension() -> ".beam"</name> + <name name="objfile_extension" arity="0"/> <fsummary>Object code file extension</fsummary> <desc> <p>Returns the object code file extension that corresponds to @@ -746,24 +671,16 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>stick_dir(Dir) -> ok | error</name> + <name name="stick_dir" arity="1"/> <fsummary>Mark a directory as sticky</fsummary> - <type> - <v>Dir = string()</v> - <v>What = term()</v> - </type> <desc> - <p>This function marks <c>Dir</c> as sticky.</p> + <p>This function marks <c><anno>Dir</anno></c> as sticky.</p> <p>Returns <c>ok</c> if successful or <c>error</c> if not.</p> </desc> </func> <func> - <name>unstick_dir(Dir) -> ok | error</name> + <name name="unstick_dir" arity="1"/> <fsummary>Remove a sticky directory mark</fsummary> - <type> - <v>Dir = string()</v> - <v>What = term()</v> - </type> <desc> <p>This function unsticks a directory which has been marked as sticky.</p> @@ -771,45 +688,39 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>is_sticky(Module) -> true | false</name> + <name name="is_sticky" arity="1"/> <fsummary>Test whether a module is sticky</fsummary> - <type> - <v>Module = atom()</v> - </type> <desc> - <p>This function returns <c>true</c> if <c>Module</c> is the + <p>This function returns <c>true</c> if <c><anno>Module</anno></c> is the name of a module that has been loaded from a sticky directory (or in other words: an attempt to reload the module will fail), - or <c>false</c> if <c>Module</c> is not a loaded module or is + or <c>false</c> if <c><anno>Module</anno></c> is not a loaded module or is not sticky.</p> </desc> </func> <func> - <name>rehash() -> ok</name> + <name name="rehash" arity="0"/> <fsummary>Rehash or create code path cache</fsummary> <desc> <p>This function creates or rehashes the code path cache.</p> </desc> </func> <func> - <name>where_is_file(Filename) -> Absname | non_existing</name> + <name name="where_is_file" arity="1"/> <fsummary>Full name of a file located in the code path</fsummary> - <type> - <v>Filename = Absname = string()</v> - </type> <desc> - <p>Searches the code path for <c>Filename</c>, a file of + <p>Searches the code path for <c><anno>Filename</anno></c>, a file of arbitrary type. If found, the full name is returned. <c>non_existing</c> is returned if the file cannot be found. The function can be useful, for example, to locate application resource files. If the code path cache is used, the code server will efficiently read the full name from - the cache, provided that <c>Filename</c> is an object code + the cache, provided that <c><anno>Filename</anno></c> is an object code file or an <c>.app</c> file.</p> </desc> </func> <func> - <name>clash() -> ok</name> + <name name="clash" arity="0"/> <fsummary>Search for modules with identical names.</fsummary> <desc> <p>Searches the entire code space for module names with @@ -817,10 +728,10 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name>is_module_native(Module) -> true | false | undefined</name> + <name>is_module_native(Module) -> boolean() | undefined</name> <fsummary>Test whether a module has native code</fsummary> <type> - <v>Module = atom()</v> + <v>Module = module()</v> </type> <desc> <p>This function returns <c>true</c> if <c>Module</c> is diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index e0feaf6ee7..861c582211 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -95,9 +95,6 @@ <datatypes> <datatype> - <name name="bindings"/> - </datatype> - <datatype> <name name="deep_list"/> </datatype> <datatype> @@ -136,12 +133,6 @@ </desc> </datatype> <datatype> - <name name="date"/> - </datatype> - <datatype> - <name name="time"/> - </datatype> - <datatype> <name name="date_time"/> <desc> <p>Must denote a valid date and time.</p> @@ -1220,15 +1211,15 @@ f.txt: {person, "kalle", 25}. <item> <p>The current system access to the file.</p> </item> - <tag><c>atime = time()</c></tag> + <tag><c>atime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>The last (local) time the file was read.</p> </item> - <tag><c>mtime = time()</c></tag> + <tag><c>mtime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>The last (local) time the file was written.</p> </item> - <tag><c>ctime = time()</c></tag> + <tag><c>ctime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>The interpretation of this time field depends on the operating system. On Unix, it is the last time @@ -1669,15 +1660,15 @@ f.txt: {person, "kalle", 25}. <p>The following fields are used from the record, if they are given.</p> <taglist> - <tag><c>atime = time()</c></tag> + <tag><c>atime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>The last (local) time the file was read.</p> </item> - <tag><c>mtime = time()</c></tag> + <tag><c>mtime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>The last (local) time the file was written.</p> </item> - <tag><c>ctime = time()</c></tag> + <tag><c>ctime = <seealso marker="#type-date_time">date_time()</seealso></c></tag> <item> <p>On Unix, any value give for this field will be ignored (the "ctime" for the file will be set to the current diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml index 5ceb82ae41..b761b6bd83 100644 --- a/lib/kernel/doc/src/gen_sctp.xml +++ b/lib/kernel/doc/src/gen_sctp.xml @@ -990,7 +990,7 @@ server(IP, Port) when is_tuple(IP) orelse IP == any orelse IP == loopback, is_integer(Port) -> - {ok,S} = gen_sctp:open([{ip,IP},{port,Port}],[{recbuf,65536}]), + {ok,S} = gen_sctp:open(Port, [{recbuf,65536}, {ip,IP}]), io:format("Listening on ~w:~w. ~w~n", [IP,Port,S]), ok = gen_sctp:listen(S, true), server_loop(S). diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml index f1d42d9faa..ebd822359a 100644 --- a/lib/kernel/doc/src/gen_tcp.xml +++ b/lib/kernel/doc/src/gen_tcp.xml @@ -37,7 +37,7 @@ binary and closing the connection:</p> <code type="none"> client() -> - SomeHostInNet = "localhost" % to make it runnable on one machine + SomeHostInNet = "localhost", % to make it runnable on one machine {ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678, [binary, {packet, 0}]), ok = gen_tcp:send(Sock, "Some Data"), diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml index 56fc1834ec..e94119845a 100644 --- a/lib/kernel/doc/src/os.xml +++ b/lib/kernel/doc/src/os.xml @@ -126,9 +126,10 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </desc> </func> <func> - <name>timestamp() -> {MegaSecs, Secs, MicroSecs}</name> + <name>timestamp() -> Timestamp</name> <fsummary>Returna a timestamp from the OS in the erlang:now/0 format</fsummary> <type> + <v>Timestamp = {MegaSecs, Secs, MicroSecs} = <seealso marker="erts:erlang#type-timestamp">erlang:timestamp()</seealso></v> <v>MegaSecs = Secs = MicroSecs = integer() >= 0</v> </type> <desc> diff --git a/lib/kernel/src/auth.erl b/lib/kernel/src/auth.erl index 25c88a4e1d..ac25ab958c 100644 --- a/lib/kernel/src/auth.erl +++ b/lib/kernel/src/auth.erl @@ -58,7 +58,7 @@ start_link() -> %%--Deprecated interface------------------------------------------------ -spec is_auth(Node) -> 'yes' | 'no' when - Node :: Node :: node(). + Node :: node(). is_auth(Node) -> case net_adm:ping(Node) of diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl index b0f99305f2..882e9625fe 100644 --- a/lib/kernel/src/code.erl +++ b/lib/kernel/src/code.erl @@ -82,7 +82,8 @@ %% add_pathsa([Dir]) -> ok %% add_pathsz([Dir]) -> ok %% del_path(Dir) -> boolean() | {error, bad_name} -%% replace_path(Name, Dir) -> true | replace_path_error() +%% replace_path(Name, Dir) -> true | {error, bad_directory | bad_name +%% | {badarg,_}} %% load_file(Module) -> {module, Module} | {error, What :: atom()} %% load_abs(File) -> {module, Module} | {error, What :: atom()} %% load_abs(File, Module) -> {module, Module} | {error, What :: atom()} @@ -113,11 +114,16 @@ %% Some types for basic exported functions of this module %%---------------------------------------------------------------------------- --type load_error_rsn() :: 'badfile' | 'native_code' | 'nofile' | 'not_purged' - | 'sticky_directory'. % for some functions only --type load_ret() :: {'error', load_error_rsn()} | {'module', atom()}. +-type load_error_rsn() :: 'badfile' + | 'native_code' + | 'nofile' + | 'not_purged' + | 'on_load' + | 'sticky_directory'. +-type load_ret() :: {'error', What :: load_error_rsn()} + | {'module', Module :: module()}. -type loaded_ret_atoms() :: 'cover_compiled' | 'preloaded'. --type loaded_filename() :: file:filename() | loaded_ret_atoms(). +-type loaded_filename() :: (Filename :: file:filename()) | loaded_ret_atoms(). %%---------------------------------------------------------------------------- %% User interface @@ -127,55 +133,74 @@ objfile_extension() -> init:objfile_extension(). --spec load_file(Module :: atom()) -> load_ret(). +-spec load_file(Module) -> load_ret() when + Module :: module(). load_file(Mod) when is_atom(Mod) -> call({load_file,Mod}). --spec ensure_loaded(Module :: atom()) -> load_ret(). +-spec ensure_loaded(Module) -> {module, Module} | {error, What} when + Module :: module(), + What :: embedded | badfile | native_code | nofile | on_load. ensure_loaded(Mod) when is_atom(Mod) -> call({ensure_loaded,Mod}). %% XXX File as an atom is allowed only for backwards compatibility. --spec load_abs(Filename :: file:filename()) -> load_ret(). +-spec load_abs(Filename) -> load_ret() when + Filename :: file:filename(). load_abs(File) when is_list(File); is_atom(File) -> call({load_abs,File,[]}). %% XXX Filename is also an atom(), e.g. 'cover_compiled' --spec load_abs(Filename :: loaded_filename(), Module :: atom()) -> load_ret(). +-spec load_abs(Filename :: loaded_filename(), Module :: module()) -> load_ret(). load_abs(File, M) when (is_list(File) orelse is_atom(File)), is_atom(M) -> call({load_abs,File,M}). %% XXX Filename is also an atom(), e.g. 'cover_compiled' --spec load_binary(Module :: atom(), Filename :: loaded_filename(), Binary :: binary()) -> load_ret(). +-spec load_binary(Module, Filename, Binary) -> + {module, Module} | {error, What} when + Module :: module(), + Filename :: loaded_filename(), + Binary :: binary(), + What :: badarg | load_error_rsn(). load_binary(Mod, File, Bin) when is_atom(Mod), (is_list(File) orelse is_atom(File)), is_binary(Bin) -> call({load_binary,Mod,File,Bin}). --spec load_native_partial(Module :: atom(), Binary :: binary()) -> load_ret(). +-spec load_native_partial(Module :: module(), Binary :: binary()) -> load_ret(). load_native_partial(Mod, Bin) when is_atom(Mod), is_binary(Bin) -> call({load_native_partial,Mod,Bin}). --spec load_native_sticky(Module :: atom(), Binary :: binary(), WholeModule :: 'false' | binary()) -> load_ret(). +-spec load_native_sticky(Module :: module(), Binary :: binary(), WholeModule :: 'false' | binary()) -> load_ret(). load_native_sticky(Mod, Bin, WholeModule) when is_atom(Mod), is_binary(Bin), (is_binary(WholeModule) orelse WholeModule =:= false) -> call({load_native_sticky,Mod,Bin,WholeModule}). --spec delete(Module :: atom()) -> boolean(). +-spec delete(Module) -> boolean() when + Module :: module(). delete(Mod) when is_atom(Mod) -> call({delete,Mod}). --spec purge(Module :: atom()) -> boolean(). +-spec purge(Module) -> boolean() when + Module :: module(). purge(Mod) when is_atom(Mod) -> call({purge,Mod}). --spec soft_purge(Module :: atom()) -> boolean(). +-spec soft_purge(Module) -> boolean() when + Module :: module(). soft_purge(Mod) when is_atom(Mod) -> call({soft_purge,Mod}). --spec is_loaded(Module :: atom()) -> {'file', loaded_filename()} | 'false'. +-spec is_loaded(Module) -> {'file', Loaded} | false when + Module :: module(), + Loaded :: loaded_filename(). is_loaded(Mod) when is_atom(Mod) -> call({is_loaded,Mod}). --spec get_object_code(Module :: atom()) -> {atom(), binary(), file:filename()} | 'error'. +-spec get_object_code(Module) -> {Module, Binary, Filename} | error when + Module :: module(), + Binary :: binary(), + Filename :: file:filename(). get_object_code(Mod) when is_atom(Mod) -> call({get_object_code, Mod}). --spec all_loaded() -> [{atom(), loaded_filename()}]. +-spec all_loaded() -> [{Module, Loaded}] when + Module :: module(), + Loaded :: loaded_filename(). all_loaded() -> call(all_loaded). -spec stop() -> no_return(). @@ -188,65 +213,86 @@ root_dir() -> call({dir,root_dir}). lib_dir() -> call({dir,lib_dir}). %% XXX is_list() is for backwards compatibility -- take out in future version --spec lib_dir(App :: atom()) -> file:filename() | {'error', 'bad_name'}. +-spec lib_dir(Name) -> file:filename() | {'error', 'bad_name'} when + Name :: atom(). lib_dir(App) when is_atom(App) ; is_list(App) -> call({dir,{lib_dir,App}}). --spec lib_dir(App :: atom(), SubDir :: atom()) -> file:filename() | {'error', 'bad_name'}. +-spec lib_dir(Name, SubDir) -> file:filename() | {'error', 'bad_name'} when + Name :: atom(), + SubDir :: atom(). lib_dir(App, SubDir) when is_atom(App), is_atom(SubDir) -> call({dir,{lib_dir,App,SubDir}}). -spec compiler_dir() -> file:filename(). compiler_dir() -> call({dir,compiler_dir}). %% XXX is_list() is for backwards compatibility -- take out in future version --spec priv_dir(App :: atom()) -> file:filename() | {'error', 'bad_name'}. +-spec priv_dir(Name) -> file:filename() | {'error', 'bad_name'} when + Name :: atom(). priv_dir(App) when is_atom(App) ; is_list(App) -> call({dir,{priv_dir,App}}). --spec stick_dir(Directory :: file:filename()) -> 'ok' | 'error'. +-spec stick_dir(Dir) -> 'ok' | 'error' when + Dir :: file:filename(). stick_dir(Dir) when is_list(Dir) -> call({stick_dir,Dir}). --spec unstick_dir(Directory :: file:filename()) -> 'ok' | 'error'. +-spec unstick_dir(Dir) -> 'ok' | 'error' when + Dir :: file:filename(). unstick_dir(Dir) when is_list(Dir) -> call({unstick_dir,Dir}). --spec stick_mod(Module :: atom()) -> 'true'. +-spec stick_mod(Module :: module()) -> 'true'. stick_mod(Mod) when is_atom(Mod) -> call({stick_mod,Mod}). --spec unstick_mod(Module :: atom()) -> 'true'. +-spec unstick_mod(Module :: module()) -> 'true'. unstick_mod(Mod) when is_atom(Mod) -> call({unstick_mod,Mod}). --spec is_sticky(Module :: atom()) -> boolean(). +-spec is_sticky(Module) -> boolean() when + Module :: module(). is_sticky(Mod) when is_atom(Mod) -> call({is_sticky,Mod}). --spec set_path(Directories :: [file:filename()]) -> - 'true' | {'error', 'bad_directory' | 'bad_path'}. +-spec set_path(Path) -> 'true' | {'error', What} when + Path :: [Dir :: file:filename()], + What :: 'bad_directory' | 'bad_path'. set_path(PathList) when is_list(PathList) -> call({set_path,PathList}). --spec get_path() -> [file:filename()]. +-spec get_path() -> Path when + Path :: [Dir :: file:filename()]. get_path() -> call(get_path). -type add_path_ret() :: 'true' | {'error', 'bad_directory'}. --spec add_path(Directory :: file:filename()) -> add_path_ret(). +-spec add_path(Dir) -> add_path_ret() when + Dir :: file:filename(). add_path(Dir) when is_list(Dir) -> call({add_path,last,Dir}). --spec add_pathz(Directory :: file:filename()) -> add_path_ret(). +-spec add_pathz(Dir) -> add_path_ret() when + Dir :: file:filename(). add_pathz(Dir) when is_list(Dir) -> call({add_path,last,Dir}). --spec add_patha(Directory :: file:filename()) -> add_path_ret(). +-spec add_patha(Dir) -> add_path_ret() when + Dir :: file:filename(). add_patha(Dir) when is_list(Dir) -> call({add_path,first,Dir}). --spec add_paths(Directories :: [file:filename()]) -> 'ok'. +-spec add_paths(Dirs) -> 'ok' when + Dirs :: [Dir :: file:filename()]. add_paths(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}). --spec add_pathsz(Directories :: [file:filename()]) -> 'ok'. +-spec add_pathsz(Dirs) -> 'ok' when + Dirs :: [Dir :: file:filename()]. add_pathsz(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}). --spec add_pathsa(Directories :: [file:filename()]) -> 'ok'. +-spec add_pathsa(Dirs) -> 'ok' when + Dirs :: [Dir :: file:filename()]. add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}). --spec del_path(Name :: file:filename() | atom()) -> boolean() | {'error', 'bad_name'}. +-spec del_path(NameOrDir) -> boolean() | {'error', What} when + NameOrDir :: Name | Dir, + Name :: atom(), + Dir :: file:filename(), + What :: 'bad_name'. del_path(Name) when is_list(Name) ; is_atom(Name) -> call({del_path,Name}). --type replace_path_error() :: {'error', 'bad_directory' | 'bad_name' | {'badarg',_}}. --spec replace_path(Name:: atom(), Dir :: file:filename()) -> 'true' | replace_path_error(). +-spec replace_path(Name, Dir) -> 'true' | {'error', What} when + Name:: atom(), + Dir :: file:filename(), + What :: 'bad_directory' | 'bad_name' | {'badarg',_}. replace_path(Name, Dir) when (is_atom(Name) orelse is_list(Name)), (is_atom(Dir) orelse is_list(Dir)) -> call({replace_path,Name,Dir}). @@ -351,10 +397,9 @@ get_mode(Flags) -> %% In that case return the name of the file which contains %% the loaded object code --type which_ret_atoms() :: loaded_ret_atoms() | 'non_existing'. - --spec which(Module :: atom()) -> file:filename() | which_ret_atoms(). - +-spec which(Module) -> Which when + Module :: module(), + Which :: file:filename() | loaded_ret_atoms() | non_existing. which(Module) when is_atom(Module) -> case is_loaded(Module) of false -> @@ -394,9 +439,9 @@ which(File, Base, [Directory|Tail]) -> %% Search the code path for a specific file. Try to locate %% it in the code path cache if possible. --spec where_is_file(Filename :: file:filename()) -> - 'non_existing' | file:filename(). - +-spec where_is_file(Filename) -> non_existing | Absname when + Filename :: file:filename(), + Absname :: file:filename(). where_is_file(File) when is_list(File) -> case call({is_cached,File}) of no -> diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index f1a8aa9f77..5e4e1b0ba8 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -100,15 +100,7 @@ | 'enotblk' | 'enotdir' | 'enotsup' | 'enxio' | 'eperm' | 'epipe' | 'erofs' | 'espipe' | 'esrch' | 'estale' | 'exdev'. --type bindings() :: erl_eval:binding_struct(). - --type date() :: {Year :: pos_integer(), - Month :: pos_integer(), - Day ::pos_integer()}. --type time() :: {Hour :: non_neg_integer(), - Minute :: non_neg_integer(), - Second :: non_neg_integer()}. --type date_time() :: {date(), time()}. +-type date_time() :: calendar:datetime(). -type posix_file_advise() :: 'normal' | 'sequential' | 'random' | 'no_reuse' | 'will_need' | 'dont_need'. @@ -920,7 +912,7 @@ eval(File) -> -spec eval(Filename, Bindings) -> ok | {error, Reason} when Filename :: name(), - Bindings :: bindings(), + Bindings :: erl_eval:binding_struct(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. @@ -948,7 +940,7 @@ path_eval(Path, File) -> {ok, FullName} | {error, Reason} when Path :: [Dir :: name()], Filename :: name(), - Bindings :: bindings(), + Bindings :: erl_eval:binding_struct(), FullName :: filename(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. @@ -979,7 +971,7 @@ script(File) -> -spec script(Filename, Bindings) -> {ok, Value} | {error, Reason} when Filename :: name(), - Bindings :: bindings(), + Bindings :: erl_eval:binding_struct(), Value :: term(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. @@ -1010,7 +1002,7 @@ path_script(Path, File) -> {ok, Value, FullName} | {error, Reason} when Path :: [Dir :: name()], Filename :: name(), - Bindings :: bindings(), + Bindings :: erl_eval:binding_struct(), Value :: term(), FullName :: filename(), Reason :: posix() | badarg | terminated | system_limit diff --git a/lib/kernel/src/inet_config.erl b/lib/kernel/src/inet_config.erl index 2458876326..1ddbdcec25 100644 --- a/lib/kernel/src/inet_config.erl +++ b/lib/kernel/src/inet_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. 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 @@ -44,26 +44,6 @@ %% -spec init() -> 'ok'. init() -> - OsType = os:type(), - case OsType of - {ose,_} -> - case init:get_argument(loader) of - {ok,[["ose_inet"]]} -> - %% port already started by prim_loader - ok; - _Other -> - %% Setup reserved port for ose_inet driver (only OSE) - case catch erlang:open_port({spawn,"ose_inet"}, [binary]) of - {'EXIT',Why} -> - error("can't open port for ose_inet: ~p", [Why]); - OseInetPort -> - erlang:display({ose_inet_port,OseInetPort}) - end - end; - _ -> - ok - end, - set_hostname(), %% Note: In shortnames (or non-distributed) mode we don't need to know @@ -71,6 +51,7 @@ init() -> %% the user to provide it (by means of inetrc), so we need to look %% for it ourselves. + OsType = os:type(), do_load_resolv(OsType, erl_dist_mode()), case OsType of @@ -226,35 +207,6 @@ do_load_resolv(vxworks, _) -> load_resolv(Resolv, resolv) end; -do_load_resolv({ose,_Type}, _) -> - inet_db:set_lookup([file, dns]), - case os:getenv("NAMESERVER") of - false -> - case os:getenv("RESOLVFILE") of - false -> - erlang:display('Warning: No NAMESERVER or RESOLVFILE specified!'), - no_resolv; - Resolv -> - load_resolv(Resolv, resolv) - end; - Ns -> - {ok,IP} = inet_parse:address(Ns), - inet_db:add_rc_list([{nameserver,IP}]) - end, - case os:getenv("DOMAIN") of - false -> - no_domain; - D -> - ok = inet_db:add_rc_list([{domain,D}]) - end, - case os:getenv("HOSTSFILE") of - false -> - erlang:display('Warning: No HOSTSFILE specified!'), - no_hosts_file; - File -> - load_hosts(File, ose) - end; - do_load_resolv(_, _) -> inet_db:set_lookup([native]). diff --git a/lib/kernel/src/inet_dns_record_adts.pl b/lib/kernel/src/inet_dns_record_adts.pl index b1d8fab939..da50c7114f 100644 --- a/lib/kernel/src/inet_dns_record_adts.pl +++ b/lib/kernel/src/inet_dns_record_adts.pl @@ -2,7 +2,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2009. All Rights Reserved. +# Copyright Ericsson AB 2009-2011. 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 @@ -73,6 +73,10 @@ while( my ($Name, $r) = each(%Names)) { # "@Values" = "V1,V2"...",VN" my @D = @DATA; foreach my $line (@D) { + # Ignore !name lines + if ($line =~ s/^\!(\S+)\s+//) { + next if $1 eq $Name; + } my $m = 1; # For leading * iterate $n times, otherwise once $line =~ s/^\s*[*]// and $m = $n; @@ -155,9 +159,9 @@ make_Name() -> \ make_Name(L) when is_list(L) -> \ make_Name(#Record{}, L). -%% Generate #Record{} with one updated field -%% -*make_Name(Field, Value) -> \ +!dns_rr_opt %% Generate #Record{} with one updated field +!dns_rr_opt %% +!dns_rr_opt *make_Name(Field, Value) -> \ #Record{Field=Value}; %% %% Update #Record{} from property list diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl index be35f99ed2..e214ffa404 100644 --- a/lib/kernel/src/rpc.erl +++ b/lib/kernel/src/rpc.erl @@ -662,9 +662,10 @@ async_call(Node, Mod, Fun, Args) -> ReplyTo ! {self(), {promise_reply, R}} %% self() is key end). --spec yield(Key) -> {value, Val} | timeout when +-spec yield(Key) -> Res | {badrpc, Reason} when Key :: key(), - Val :: (Res :: term()) | {badrpc, Reason :: term()}. + Res :: term(), + Reason :: term(). yield(Key) when is_pid(Key) -> {value,R} = do_yield(Key, infinity), diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index d8a5519195..b734d7fd98 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -400,6 +400,7 @@ open_fd(Config) when is_list(Config) -> {ok,S1} = gen_udp:open(0), {ok,P2} = inet:port(S1), {ok,FD} = prim_inet:getfd(S1), + {error,einval} = gen_udp:open(P2, [inet6, {fd,FD}]), {ok,S2} = gen_udp:open(P2, [{fd,FD}]), {ok,S3} = gen_udp:open(0), {ok,P3} = inet:port(S3), diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c index d61ce940c3..11e311d72d 100644 --- a/lib/odbc/c_src/odbcserver.c +++ b/lib/odbc/c_src/odbcserver.c @@ -90,7 +90,7 @@ Datatype - USER_INT | USER_SMALL_INT | {USER_DECIMAL, Precision, Scale} | {USER_NMERIC, Precision, Scale} | {USER_CHAR, Max} | {USER_VARCHAR, Max} | {USER_WVARCHAR, Max} | {USER_FLOAT, Precision} | USER_REAL | USER_DOUBLE | - USER_TIMESTAMP + USER_TIMESTAMP | {USER_WLONGVARCHAR, Max} Scale - integer Precision - integer Max - integer @@ -173,7 +173,7 @@ static db_result_msg encode_row_count(SQLINTEGER num_of_rows, db_state *state); static void encode_column_dyn(db_column column, int column_nr, db_state *state); -static void encode_data_type(SQLINTEGER sql_type, SQLINTEGER size, +static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size, SQLSMALLINT decimal_digits, db_state *state); static Boolean decode_params(db_state *state, byte *buffer, int *index, param_array **params, int i, int j); @@ -221,7 +221,7 @@ static void init_param_column(param_array *params, byte *buffer, int *index, int num_param_values, db_state* state); static void init_param_statement(int cols, - int num_param_values, + SQLLEN num_param_values, db_state *state, param_status *status); @@ -435,7 +435,7 @@ static db_result_msg db_connect(byte *args, db_state *state) diagnos diagnos; byte *connStrIn; int erl_auto_commit_mode, erl_trace_driver, - use_srollable_cursors, tuple_row_state, binary_strings; + use_srollable_cursors, tuple_row_state, binary_strings; erl_auto_commit_mode = args[0]; erl_trace_driver = args[1]; @@ -757,8 +757,9 @@ static db_result_msg db_select(byte *args, db_state *state) static db_result_msg db_param_query(byte *buffer, db_state *state) { byte *sql; - db_result_msg msg; - int i, num_param_values, ver = 0, + db_result_msg msg; + SQLLEN num_param_values; + int i, ver = 0, erl_type = 0, index = 0, size = 0, cols = 0; long long_num_param_values; param_status param_status; @@ -785,7 +786,7 @@ static db_result_msg db_param_query(byte *buffer, db_state *state) ei_decode_long(buffer, &index, &long_num_param_values); - num_param_values = (int)long_num_param_values; + num_param_values = (SQLLEN)long_num_param_values; ei_decode_list_header(buffer, &index, &cols); @@ -1002,12 +1003,16 @@ static db_result_msg encode_result(db_state *state) db_result_msg msg; int elements, update, num_of_rows = 0; char *atom; + diagnos diagnos; msg = encode_empty_message(); if(!sql_success(SQLNumResultCols(statement_handle(state), &num_of_columns))) { - DO_EXIT(EXIT_COLS); + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + clean_state(state); + return msg; } if (num_of_columns == 0) { @@ -1021,7 +1026,10 @@ static db_result_msg encode_result(db_state *state) } if(!sql_success(SQLRowCount(statement_handle(state), &RowCountPtr))) { - DO_EXIT(EXIT_ROWS); + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + clean_state(state); + return msg; } if(param_query(state) && update) { @@ -1220,7 +1228,7 @@ static db_result_msg encode_column_name_list(SQLSMALLINT num_of_columns, &nullable))) DO_EXIT(EXIT_DESC); - if(sql_type == SQL_LONGVARCHAR || sql_type == SQL_LONGVARBINARY) + if(sql_type == SQL_LONGVARCHAR || sql_type == SQL_LONGVARBINARY || sql_type == SQL_WLONGVARCHAR) size = MAXCOLSIZE; (columns(state)[i]).type.decimal_digits = dec_digits; @@ -1452,7 +1460,7 @@ static void encode_column_dyn(db_column column, int column_nr, } } -static void encode_data_type(SQLINTEGER sql_type, SQLINTEGER size, +static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size, SQLSMALLINT decimal_digits, db_state *state) { switch(sql_type) { @@ -1529,6 +1537,11 @@ static void encode_data_type(SQLINTEGER sql_type, SQLINTEGER size, case SQL_LONGVARCHAR: ei_x_encode_atom(&dynamic_buffer(state), "SQL_LONGVARCHAR"); break; + case SQL_WLONGVARCHAR: + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_atom(&dynamic_buffer(state), "sql_wlongvarchar"); + ei_x_encode_long(&dynamic_buffer(state), size); + break; case SQL_VARBINARY: ei_x_encode_atom(&dynamic_buffer(state), "SQL_VARBINARY"); break; @@ -2007,7 +2020,7 @@ static void init_driver(int erl_auto_commit_mode, int erl_trace_driver, db_state *state) { - int auto_commit_mode, trace_driver; + SQLLEN auto_commit_mode, trace_driver; if(erl_auto_commit_mode == ON) { auto_commit_mode = SQL_AUTOCOMMIT_ON; @@ -2057,7 +2070,7 @@ static void init_param_column(param_array *params, byte *buffer, int *index, ei_decode_long(buffer, index, &user_type); - params->type.strlen_or_indptr = (SQLINTEGER)NULL; + params->type.strlen_or_indptr = (SQLLEN)NULL; params->type.strlen_or_indptr_array = NULL; params->type.decimal_digits = (SQLINTEGER)0; @@ -2131,10 +2144,14 @@ static void init_param_column(param_array *params, byte *buffer, int *index, break; case USER_WCHAR: case USER_WVARCHAR: - if(user_type == USER_WCHAR) { - params->type.sql = SQL_WCHAR; - } else { - params->type.sql = SQL_WVARCHAR; + case USER_WLONGVARCHAR: + switch (user_type) { + case USER_WCHAR: + params->type.sql = SQL_WCHAR; break; + case USER_WVARCHAR: + params->type.sql = SQL_WVARCHAR; break; + default: + params->type.sql = SQL_WLONGVARCHAR; break; } ei_decode_long(buffer, index, &length); /* Max string length + string terminator */ @@ -2206,7 +2223,7 @@ static void init_param_column(param_array *params, byte *buffer, int *index, } -static void init_param_statement(int cols, int num_param_values, +static void init_param_statement(int cols, SQLLEN num_param_values, db_state *state, param_status *status) { int i; @@ -2234,11 +2251,11 @@ static void init_param_statement(int cols, int num_param_values, DO_EXIT(EXIT_PARAM_ARRAY); } - /* Note the (int *) cast is correct as the API function SQLSetStmtAttr + /* Note the (SQLLEN *) cast is correct as the API function SQLSetStmtAttr takes either an interger or a pointer depending on the attribute */ if(!sql_success(SQLSetStmtAttr(statement_handle(state), SQL_ATTR_PARAMSET_SIZE, - (int *)num_param_values, + (SQLLEN *)num_param_values, 0))) { DO_EXIT(EXIT_PARAM_ARRAY); } @@ -2300,6 +2317,7 @@ static db_result_msg map_sql_2_c_column(db_column* column) break; case SQL_WCHAR: case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: column -> type.len = (column -> type.col_size + 1)*sizeof(SQLWCHAR); column -> type.c = SQL_C_WCHAR; column -> type.strlen_or_indptr = SQL_NTS; @@ -2308,21 +2326,21 @@ static db_result_msg map_sql_2_c_column(db_column* column) case SQL_DECIMAL: map_dec_num_2_c_column(&(column -> type), column -> type.col_size, column -> type.decimal_digits); - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_TINYINT: case SQL_INTEGER: case SQL_SMALLINT: column -> type.len = sizeof(SQLINTEGER); column -> type.c = SQL_C_SLONG; - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: column -> type.len = sizeof(double); column -> type.c = SQL_C_DOUBLE; - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_TYPE_DATE: case SQL_TYPE_TIME: @@ -2334,17 +2352,17 @@ static db_result_msg map_sql_2_c_column(db_column* column) case SQL_TYPE_TIMESTAMP: column -> type.len = sizeof(TIMESTAMP_STRUCT); column -> type.c = SQL_C_TYPE_TIMESTAMP; - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_BIGINT: column -> type.len = DEC_NUM_LENGTH; column -> type.c = SQL_C_CHAR; - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_BIT: column -> type.len = sizeof(byte); column -> type.c = SQL_C_BIT; - column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + column -> type.strlen_or_indptr = (SQLLEN)NULL; break; case SQL_UNKNOWN_TYPE: msg = encode_error_message("Unknown column type"); diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h index 3e2b22ab7d..314fbf32c6 100644 --- a/lib/odbc/c_src/odbcserver.h +++ b/lib/odbc/c_src/odbcserver.h @@ -115,6 +115,7 @@ #define USER_WCHAR 12 #define USER_WVARCHAR 13 #define USER_TIMESTAMP 14 +#define USER_WLONGVARCHAR 15 /*------------------------ TYPDEFS ----------------------------------*/ diff --git a/lib/odbc/doc/src/databases.xml b/lib/odbc/doc/src/databases.xml index a6ba0e5245..09f5a5af5b 100644 --- a/lib/odbc/doc/src/databases.xml +++ b/lib/odbc/doc/src/databases.xml @@ -205,6 +205,10 @@ when p >= 16 </cell> <cell align="left" valign="middle">String | Binary (configurable)</cell> </row> <row> + <cell align="left" valign="middle">SQL_WLONGVARCHAR(size) </cell> + <cell align="left" valign="middle">Unicode binary encoded as UTF16 little endian.</cell> + </row> + <row> <cell align="left" valign="middle">SQL_BINARY</cell> <cell align="left" valign="middle">String | Binary (configurable)</cell> </row> diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml index 70d8cfbe22..11ca97f743 100644 --- a/lib/odbc/doc/src/odbc.xml +++ b/lib/odbc/doc/src/odbc.xml @@ -102,7 +102,7 @@ {sql_decimal, precision(), scale()} | {sql_numeric, precision(), scale()} | {sql_char, size()} | {sql_wchar, size()} | {sql_varchar, size()} | {sql_wvarchar, size()}| {sql_float, precision()} | - {sql_float, precision()} | sql_real | sql_double | sql_bit | atom() + {sql_wlongvarchar, size()} | {sql_float, precision()} | sql_real | sql_double | sql_bit | atom() </code> <code type="none"> precision() = integer() </code> diff --git a/lib/odbc/src/odbc.appup.src b/lib/odbc/src/odbc.appup.src index 2a6667ccd3..f3a3af8c29 100644 --- a/lib/odbc/src/odbc.appup.src +++ b/lib/odbc/src/odbc.appup.src @@ -1,8 +1,10 @@ %% -*- erlang -*- {"%VSN%", [ - {"2.10.9", [{restart_application, ssl}]} + {"2.10.10", [{restart_application, odbc}]}, + {"2.10.9", [{restart_application, odbc}]} ], [ - {"2.10.9", [{restart_application, ssl}]} + {"2.10.10", [{restart_application, odbc}]}, + {"2.10.9", [{restart_application, odbc}]} ]}. diff --git a/lib/odbc/src/odbc.erl b/lib/odbc/src/odbc.erl index 83d9f33102..68497292db 100644 --- a/lib/odbc/src/odbc.erl +++ b/lib/odbc/src/odbc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2010. All Rights Reserved. +%% Copyright Ericsson AB 1999-2011. 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 @@ -431,7 +431,7 @@ init(Args) -> erlang:monitor(process, ClientPid), - Inet = case gen_tcp:listen(0, [inet6]) of + Inet = case gen_tcp:listen(0, [inet6, {ip, loopback}]) of {ok, Dummyport} -> gen_tcp:close(Dummyport), inet6; @@ -925,6 +925,9 @@ fix_params({{sql_wchar, Max}, InOut, Values}) -> fix_params({{sql_wvarchar, Max}, InOut, Values}) -> NewValues = string_terminate(Values), {?USER_WVARCHAR, Max, fix_inout(InOut), NewValues}; +fix_params({{sql_wlongvarchar, Max}, InOut, Values}) -> + NewValues = string_terminate(Values), + {?USER_WLONGVARCHAR, Max, fix_inout(InOut), NewValues}; fix_params({{sql_float, Precision}, InOut, Values}) -> {?USER_FLOAT, Precision, fix_inout(InOut), Values}; fix_params({sql_real, InOut, Values}) -> diff --git a/lib/odbc/src/odbc_internal.hrl b/lib/odbc/src/odbc_internal.hrl index aa60120f9a..c0e7d9657b 100644 --- a/lib/odbc/src/odbc_internal.hrl +++ b/lib/odbc/src/odbc_internal.hrl @@ -72,6 +72,7 @@ -define(USER_WCHAR, 12). -define(USER_WVARCHAR, 13). -define(USER_TIMESTAMP, 14). +-define(USER_WLONGVARCHAR, 15). %% INPUT & OUTPUT TYPE -define(IN, 0). diff --git a/lib/odbc/test/Makefile b/lib/odbc/test/Makefile index ec2bcc67b5..bc6449242e 100644 --- a/lib/odbc/test/Makefile +++ b/lib/odbc/test/Makefile @@ -34,7 +34,8 @@ MODULES= \ odbc_test_lib \ oracle \ sqlserver \ - postgres + postgres \ + mysql EBIN = . diff --git a/lib/odbc/test/mysql.erl b/lib/odbc/test/mysql.erl new file mode 100644 index 0000000000..c990793213 --- /dev/null +++ b/lib/odbc/test/mysql.erl @@ -0,0 +1,277 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. 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(mysql). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +%------------------------------------------------------------------------- +connection_string() -> + case test_server:os_type() of + {unix, linux} -> + "DSN=MySQL;Database=odbctest;Uid=odbctest;Pwd=gurka;CHARSET=utf8;SSTMT=SET NAMES 'utf8';"; + {unix, sunos} -> + solaris_str(); + {unix, darwin} -> + "DSN=MySQLMac;Database=odbctest;Uid=odbctest;Pwd=gurka;CHARSET=utf8;SSTMT=SET NAMES 'utf8';" + end. + +solaris_str() -> + case erlang:system_info(system_architecture) of + "sparc" ++ _ -> + "DSN=MySQLSolaris10;Database=odbctest;Uid=odbctest;Pwd=gurka;CHARSET=utf8;SSTMT=SET NAMES 'utf8';"; + "i386" ++ _ -> + "DSN=MySQLSolaris10i386;Database=odbctest;Uid=odbctest;Pwd=gurka;CHARSET=utf8;SSTMT=SET NAMES 'utf8';" + end. + +%------------------------------------------------------------------------- +insert_result() -> + {selected,["ID","DATA"],[{1,"bar"}]}. + +update_result() -> + {selected,["ID","DATA"],[{1,"foo"}]}. + +selected_ID(N, next) -> + {selected,["ID"],[{N}]}; + +selected_ID(_, _) -> + {error, driver_does_not_support_function}. + +selected_next_N(1)-> + {selected,["ID"], + [{1}, + {2}, + {3}]}; + +selected_next_N(2)-> + {selected,["ID"], + [{4}, + {5}]}. + +selected_relative_N(_)-> + {error, driver_does_not_support_function}. + +selected_absolute_N(_)-> + {error, driver_does_not_support_function}. + +selected_list_rows() -> + {selected,["ID", "DATA"],[[1, "bar"],[2,"foo"]]}. + +first_list_rows() -> + {error, driver_does_not_support_function}. +last_list_rows() -> + {error, driver_does_not_support_function}. +prev_list_rows() -> + {error, driver_does_not_support_function}. +next_list_rows() -> + {selected,["ID","DATA"],[[1,"bar"]]}. + +multiple_select()-> + [{selected,["ID", "DATA"],[{1, "bar"},{2, "foo"}]}, + {selected,["ID"],[{"foo"}]}]. + +multiple_mix()-> + [{updated, 1},{updated, 1}, + {selected,["ID", "DATA"],[{1, "foobar"},{2, "foo"}]}, + {updated, 1}, {selected,["DATA"],[{"foo"}]}]. + +%------------------------------------------------------------------------- +var_char_min() -> + 0. +var_char_max() -> + 65535. + +create_var_char_table(Size) -> + " (FIELD varchar(" ++ integer_to_list(Size) ++ "))". + +%------------------------------------------------------------------------- +text_min() -> + 1. +text_max() -> + 2147483646. % 2147483647. %% 2^31 - 1 + +create_text_table() -> + " (FIELD text)". + +%------------------------------------------------------------------------- +create_timestamp_table() -> + " (FIELD TIMESTAMP)". + +%------------------------------------------------------------------------- +tiny_int_min() -> + -128. +tiny_int_max() -> + 127. + +create_tiny_int_table() -> + " (FIELD tinyint)". + +tiny_int_min_selected() -> + {selected,["FIELD"],[{tiny_int_min()}]}. + +tiny_int_max_selected() -> + {selected,["FIELD"], [{tiny_int_max()}]}. + +%------------------------------------------------------------------------- +small_int_min() -> + -32768. +small_int_max() -> + 32767. + +create_small_int_table() -> + " (FIELD smallint)". + +small_int_min_selected() -> + {selected,["FIELD"],[{-32768}]}. + +small_int_max_selected() -> + {selected,["FIELD"], [{32767}]}. + +%------------------------------------------------------------------------- +int_min() -> + -2147483648. +int_max() -> + 2147483647. + +create_int_table() -> + " (FIELD int)". + +int_min_selected() -> + {selected,["FIELD"],[{-2147483648}]}. + +int_max_selected() -> + {selected,["FIELD"], [{2147483647}]}. + +%------------------------------------------------------------------------- +big_int_min() -> + -9223372036854775808. + +big_int_max() -> + 9223372036854775807. + +create_big_int_table() -> + " (FIELD bigint )". + +big_int_min_selected() -> + {selected,["FIELD"], [{"-9223372036854775808"}]}. + +big_int_max_selected() -> + {selected,["FIELD"], [{"9223372036854775807"}]}. + +%------------------------------------------------------------------------- +bit_false() -> + 0. +bit_true() -> + 1. + +create_bit_table() -> + " (FIELD bit)". + +bit_false_selected() -> + {selected,["FIELD"],[{"0"}]}. + +bit_true_selected() -> + {selected,["FIELD"], [{"1"}]}. + +%------------------------------------------------------------------------- + +%% Do not test float min/max as value is only theoretical defined in +%% mysql and may vary depending on hardware. + +create_float_table() -> + " (FIELD float)". + +float_zero_selected() -> + {selected,["FIELD"],[{0.00000e+0}]}. + +%------------------------------------------------------------------------- +real_min() -> + -3.40e+38. +real_max() -> + 3.40e+38. + +real_underflow() -> + "-3.41e+38". + +real_overflow() -> + "3.41e+38". + +create_real_table() -> + " (FIELD real)". + +real_zero_selected() -> + {selected,["FIELD"],[{0.00000e+0}]}. + +%------------------------------------------------------------------------- +param_select_small_int() -> + {selected,["FIELD"],[{1}, {2}]}. + +param_select_int() -> + Int = small_int_max() + 1, + {selected,["FIELD"],[{1}, {Int}]}. + +param_select_decimal() -> + {selected,["FIELD"],[{1},{2}]}. + +param_select_numeric() -> + {selected,["FIELD"],[{1},{2}]}. + +param_select_float() -> + {selected,["FIELD"],[{1.30000},{1.20000}]}. + +param_select_real() -> + {selected,["FIELD"],[{1.30000},{1.20000}]}. + +param_select_double() -> + {selected,["FIELD"],[{1.30000},{1.20000}]}. + +param_select_mix() -> + {selected,["ID","DATA"],[{1, "foo"}, {2, "bar"}]}. + +param_update() -> + {selected,["ID","DATA"],[{1, "foobar"}, {2, "foobar"}, {3, "baz"}]}. + +param_delete() -> + {selected,["ID","DATA"],[{3, "baz"}]}. + +param_select() -> + {selected,["ID","DATA"],[{1, "foo"},{3, "foo"}]}. + +%------------------------------------------------------------------------- +describe_integer() -> + {ok,[{"myint1",sql_smallint}, + {"myint2",sql_integer}, + {"myint3",sql_integer}]}. + +describe_string() -> + {ok,[{"str1",{sql_char,10}}, + {"str2",{sql_char,10}}, + {"str3",{sql_varchar,10}}, + {"str4",{sql_varchar,10}}]}. + +describe_floating() -> + {ok,[{"f",sql_real},{"r",sql_double},{"d",sql_double}]}. +describe_dec_num() -> + {ok,[{"mydec",{sql_decimal,9,3}},{"mynum",{sql_decimal,9,2}}]}. + +describe_timestamp() -> + {ok, [{"FIELD", sql_timestamp}]}. diff --git a/lib/odbc/test/odbc.dynspec b/lib/odbc/test/odbc.dynspec deleted file mode 100644 index bb15edceed..0000000000 --- a/lib/odbc/test/odbc.dynspec +++ /dev/null @@ -1,31 +0,0 @@ -%% -*- erlang -*- -%% You can test this file using this command. -%% file:script("odbc.dynspec", [{'Os',"Unix"}]). - -Exists = -fun() -> - case code:lib_dir(odbc) of - {error,bad_name} -> - false; - P -> - %% Make sure that the odbc directory really - %% contains the application (and not only documentation). - case filelib:is_file(filename:join(P, "ebin/odbc.beam")) of - false -> false; - true -> - %% We know that we don't have any odbc libraries - %% installed on this computer. - {ok,Host} = inet:gethostname(), - Host =/= "netsim200" - end - end -end, -case Exists() of - false -> - NoOdbc = "No odbc application", - [{skip, {odbc_connect_SUITE, NoOdbc}}, - {skip, {odbc_data_type_SUITE, NoOdbc}}, - {skip, {odbc_query_SUITE, NoOdbc}}]; - true -> - [] -end. diff --git a/lib/odbc/test/odbc.spec b/lib/odbc/test/odbc.spec index edaf821c91..653f1a780e 100644 --- a/lib/odbc/test/odbc.spec +++ b/lib/odbc/test/odbc.spec @@ -1,25 +1 @@ {suites,"../odbc_test",all}. -{skip_cases,"../odbc_test",odbc_data_type_SUITE, - [varchar_upper_limit], - "Known bug in database"}. -{skip_cases,"../odbc_test",odbc_data_type_SUITE, - [text_upper_limit], - "Consumes too much resources"}. -{skip_cases,"../odbc_test",odbc_data_type_SUITE, - [bit_true], - "Not supported by driver"}. -{skip_cases,"../odbc_test",odbc_data_type_SUITE, - [bit_false], - "Not supported by driver"}. -{skip_cases,"../odbc_test",odbc_query_SUITE, - [multiple_select_result_sets], - "Not supported by driver"}. -{skip_cases,"../odbc_test",odbc_query_SUITE, - [multiple_mix_result_sets], - "Not supported by driver"}. -{skip_cases,"../odbc_test",odbc_query_SUITE, - [multiple_result_sets_error], - "Not supported by driver"}. -{skip_cases,"../odbc_test",odbc_query_SUITE, - [param_insert_tiny_int], - "Not supported by driver"}. diff --git a/lib/odbc/test/odbc.spec.win b/lib/odbc/test/odbc.spec.win deleted file mode 100644 index 1fd349d2c3..0000000000 --- a/lib/odbc/test/odbc.spec.win +++ /dev/null @@ -1,5 +0,0 @@ -{topcase, {dir, "../odbc_test"}}. -{skip, {odbc_data_type_SUITE, big_int_lower_limit, "Not supported by sqlserver 7.0"}}. -{skip, {odbc_data_type_SUITE, big_int_upper_limit, "Not supported by sqlserver7.0"}}. -{skip, {odbc_data_type_SUITE, text_upper_limit, "Consumes too much resources"}}. - diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl index 6a2268f40e..a076c4dfff 100644 --- a/lib/odbc/test/odbc_connect_SUITE.erl +++ b/lib/odbc/test/odbc_connect_SUITE.erl @@ -76,16 +76,26 @@ end_per_group(_GroupName, Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- -init_per_suite(Config) -> - application:start(odbc), - case catch odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]) of - {ok, Ref} -> - odbc:disconnect(Ref), - [{tableName, odbc_test_lib:unique_table_name()} | Config]; - _ -> - {skip, "ODBC is not properly setup"} - end. +init_per_suite(Config) when is_list(Config) -> + case odbc_test_lib:skip() of + true -> + {skip, "ODBC not supported"}; + false -> + case (catch odbc:start()) of + ok -> + case catch odbc:connect(?RDBMS:connection_string(), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()) of + {ok, Ref} -> + odbc:disconnect(Ref), + [{tableName, odbc_test_lib:unique_table_name()} | Config]; + _ -> + {skip, "ODBC is not properly setup"} + end; + _ -> + {skip,"ODBC not startable"} + end + end. + %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ %% Config - [tuple()] @@ -93,8 +103,7 @@ init_per_suite(Config) -> %% Description: Cleanup after the whole suite %%-------------------------------------------------------------------- end_per_suite(_Config) -> - application:stop(odbc), - ok. + application:stop(odbc). %%-------------------------------------------------------------------- %% Function: init_per_testcase(Case, Config) -> Config @@ -124,15 +133,13 @@ init_per_testcase(_TestCase, Config) -> %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_TestCase, Config) -> - %% Clean up if needed Table = ?config(tableName, Config), - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), - Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), + Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table), io:format("Drop table: ~p ~p~n", [Table, Result]), odbc:disconnect(Ref), Dog = ?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. + test_server:timetrap_cancel(Dog). %%------------------------------------------------------------------------- %% Test cases starts here. @@ -142,13 +149,15 @@ commit(doc)-> commit(suite) -> []; commit(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), + TransStr = transaction_support_str(?RDBMS), + {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10))"), + " (ID integer, DATA varchar(10))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1,'bar')"), @@ -173,50 +182,47 @@ commit(Config) -> {'EXIT', {function_clause, _}} = (catch odbc:commit(Ref, commit, -1)), - ok = odbc:disconnect(Ref), - - ok. + ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- rollback(doc)-> ["Test the use of explicit rollback"]; rollback(suite) -> []; rollback(Config) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), - {updated, _} = - odbc:sql_query(Ref, + TransStr = transaction_support_str(?RDBMS), + + {updated, _} = + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10))"), - {updated, 1} = + " (ID integer, DATA varchar(10))" ++ TransStr), + {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), - {updated, 1} = + {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ - " SET DATA = 'foo' WHERE ID = 1"), + " SET DATA = 'foo' WHERE ID = 1"), ok = odbc:commit(Ref, rollback), InsertResult = ?RDBMS:insert_result(), - InsertResult = + InsertResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), - - {updated, 1} = + {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ - " SET DATA = 'foo' WHERE ID = 1"), + " SET DATA = 'foo' WHERE ID = 1"), ok = odbc:commit(Ref, rollback, ?TIMEOUT), InsertResult = ?RDBMS:insert_result(), - InsertResult = + InsertResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), + {'EXIT', {function_clause, _}} = + (catch odbc:commit(Ref, rollback, -1)), - {'EXIT', {function_clause, _}} = - (catch odbc:commit(Ref, rollback, -1)), - - ok = odbc:disconnect(Ref), - ok. + ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- not_explicit_commit(doc) -> @@ -224,20 +230,20 @@ not_explicit_commit(doc) -> not_explicit_commit(suite) -> []; not_explicit_commit(_Config) -> {ok, Ref} = - odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}]), + odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}] ++ + odbc_test_lib:platform_options()), {error, _} = odbc:commit(Ref, commit), - ok = odbc:disconnect(Ref), - ok. + ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- not_exist_db(doc) -> ["Tests valid data format but invalid data in the connection parameters."]; not_exist_db(suite) -> []; not_exist_db(_Config) -> - {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", []), + {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", + odbc_test_lib:platform_options()), %% So that the odbc control server can be stoped "in the correct way" - test_server:sleep(100), - ok. + test_server:sleep(100). %%------------------------------------------------------------------------- no_c_node(doc) -> @@ -252,7 +258,8 @@ no_c_node(_Config) -> FileName2 = filename:nativename(filename:join(Dir, "odbcsrv")), ok = file:rename(FileName1, FileName2), Result = - case catch odbc:connect(?RDBMS:connection_string(), []) of + case catch odbc:connect(?RDBMS:connection_string(), + odbc_test_lib:platform_options()) of {error, port_program_executable_not_found} -> ok; Else -> @@ -267,7 +274,7 @@ port_dies(doc) -> "Tests what happens if the port program dies"; port_dies(suite) -> []; port_dies(_Config) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {status, _} = process_info(Ref, status), process_flag(trap_exit, true), Port = lists:last(erlang:ports()), @@ -275,26 +282,23 @@ port_dies(_Config) -> %% Wait for exit_status from port 5000 ms (will not get a exit %% status in this case), then wait a little longer to make sure %% the port and the controlprocess has had time to terminate. - test_server:sleep(7000), - undefined = process_info(Ref, status), - ok. + test_server:sleep(10000), + undefined = process_info(Ref, status). %%------------------------------------------------------------------------- control_process_dies(doc) -> "Tests what happens if the Erlang control process dies"; control_process_dies(suite) -> []; control_process_dies(_Config) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), process_flag(trap_exit, true), Port = lists:last(erlang:ports()), {connected, Ref} = erlang:port_info(Port, connected), exit(Ref, kill), - test_server:sleep(100), - undefined = erlang:port_info(Port, connected), + test_server:sleep(500), + undefined = erlang:port_info(Port, connected). %% Check for c-program still running, how? - ok. -%%------------------------------------------------------------------------- %%------------------------------------------------------------------------- client_dies_normal(doc) -> @@ -319,7 +323,7 @@ client_dies_normal(Config) when is_list(Config) -> end. client_normal(Pid) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> @@ -351,7 +355,7 @@ client_dies_timeout(Config) when is_list(Config) -> end. client_timeout(Pid) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> @@ -383,7 +387,7 @@ client_dies_error(Config) when is_list(Config) -> end. client_error(Pid) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> @@ -398,7 +402,10 @@ connect_timeout(doc) -> connect_timeout(suite) -> []; connect_timeout(Config) when is_list(Config) -> {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(), - [{timeout, 0}])), + [{timeout, 0}] ++ + odbc_test_lib:platform_options())), + %% Need to return ok here "{'EXIT',timeout} return value" will + %% be interpreted as that the testcase has timed out. ok. %%------------------------------------------------------------------------- timeout(doc) -> @@ -411,10 +418,12 @@ timeout(Config) when is_list(Config) -> [{auto_commit, off}]), Table = ?config(tableName, Config), + TransStr = transaction_support_str(?RDBMS), + {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), + " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), @@ -446,14 +455,12 @@ timeout(Config) when is_list(Config) -> ["DATA"] = odbc_test_lib:to_upper(Fields), ok = odbc:commit(Ref, commit), - ok = odbc:disconnect(Ref), - ok. - + ok = odbc:disconnect(Ref). update_table_timeout(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of @@ -474,15 +481,16 @@ update_table_timeout(Table, TimeOut, Pid) -> odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"), ["DATA"] = odbc_test_lib:to_upper(Fields), - {updated, 1} = odbc:sql_query(Ref, UpdateQuery, TimeOut), + %% Do not check {updated, 1} as some drivers will return 0 + %% even though the update is done, which is checked by the test + %% case when the altered message is recived. + {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut), ok = odbc:commit(Ref, commit), Pid ! altered, - ok = odbc:disconnect(Ref), - - ok. + ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- many_timeouts(doc) -> ["Tests that many consecutive timeouts lead to that the connection " @@ -490,14 +498,15 @@ many_timeouts(doc) -> many_timeouts(suite) -> []; many_timeouts(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), + TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), + " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), @@ -517,22 +526,20 @@ many_timeouts(Config) when is_list(Config) -> end, ok = odbc:commit(Ref, commit), - ok = odbc:disconnect(Ref), - ok. + ok = odbc:disconnect(Ref). update_table_many_timeouts(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", ok = loop_many_timouts(Ref, UpdateQuery, TimeOut), Pid ! many_timeouts_occurred, - ok = odbc:disconnect(Ref), - ok. + ok = odbc:disconnect(Ref). loop_many_timouts(Ref, UpdateQuery, TimeOut) -> @@ -546,18 +553,19 @@ loop_many_timouts(Ref, UpdateQuery, TimeOut) -> end. %%------------------------------------------------------------------------- timeout_reset(doc) -> - ["Check that the number of consecutive timouts is reset to 0 when " + ["Check that the number of consecutive timouts is reset to 0 when " "a successful call to the database is made."]; timeout_reset(suite) -> []; timeout_reset(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), + TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), + " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), @@ -593,13 +601,12 @@ timeout_reset(Config) when is_list(Config) -> ["DATA"] = odbc_test_lib:to_upper(Fields), ok = odbc:commit(Ref, commit), - ok = odbc:disconnect(Ref), - ok. + ok = odbc:disconnect(Ref). update_table_timeout_reset(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", ok = loop_timout_reset(Ref, UpdateQuery, TimeOut, @@ -616,15 +623,16 @@ update_table_timeout_reset(Table, TimeOut, Pid) -> odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"), ["DATA"] = odbc_test_lib:to_upper(Fields), - {updated,1} = odbc:sql_query(Ref, UpdateQuery, TimeOut), + %% Do not check {updated, 1} as some drivers will return 0 + %% even though the update is done, which is checked by the test + %% case when the altered message is recived. + {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut), ok = odbc:commit(Ref, commit), Pid ! altered, - ok = odbc:disconnect(Ref), - - ok. + ok = odbc:disconnect(Ref). loop_timout_reset(_, _, _, 0) -> ok; @@ -648,13 +656,14 @@ disconnect_on_timeout(suite) -> []; disconnect_on_timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), + TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), + " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), @@ -678,7 +687,7 @@ disconnect_on_timeout(Config) when is_list(Config) -> update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{auto_commit, off}]), + [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of @@ -695,7 +704,7 @@ connection_closed(doc) -> " use a connection that has been closed"]; connection_closed(suite) -> []; connection_closed(Config) when is_list(Config) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Table = ?config(tableName, Config), {updated, _} = @@ -714,8 +723,7 @@ connection_closed(Config) when is_list(Config) -> {error, connection_closed} = odbc:next(Ref), {error, connection_closed} = odbc:prev(Ref), {error, connection_closed} = odbc:select(Ref, next, 3), - {error, connection_closed} = odbc:commit(Ref, commit), - ok. + {error, connection_closed} = odbc:commit(Ref, commit). %%------------------------------------------------------------------------- disable_scrollable_cursors(doc) -> @@ -753,8 +761,7 @@ disable_scrollable_cursors(Config) when is_list(Config) -> {error, scrollable_cursors_disabled} = odbc:select(Ref, {absolute, 2}, 5), - {selected, _ColNames,[]} = odbc:select(Ref, next, 1), - ok. + {selected, _ColNames,[]} = odbc:select(Ref, next, 1). %%------------------------------------------------------------------------- return_rows_as_lists(doc)-> @@ -763,7 +770,7 @@ return_rows_as_lists(doc)-> return_rows_as_lists(suite) -> []; return_rows_as_lists(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{tuple_row, off}]), + [{tuple_row, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), @@ -784,16 +791,21 @@ return_rows_as_lists(Config) when is_list(Config) -> {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table), - First = ?RDBMS:first_list_rows(), - Last = ?RDBMS:last_list_rows(), - Prev = ?RDBMS:prev_list_rows(), - Next = ?RDBMS:next_list_rows(), - - Last = odbc:last(Ref), - Prev = odbc:prev(Ref), - First = odbc:first(Ref), - Next = odbc:next(Ref), - ok. + case proplists:get_value(scrollable_cursors, odbc_test_lib:platform_options()) of + off -> + Next = ?RDBMS:next_list_rows(), + Next = odbc:next(Ref); + _ -> + First = ?RDBMS:first_list_rows(), + Last = ?RDBMS:last_list_rows(), + Prev = ?RDBMS:prev_list_rows(), + Next = ?RDBMS:next_list_rows(), + + Last = odbc:last(Ref), + Prev = odbc:prev(Ref), + First = odbc:first(Ref), + Next = odbc:next(Ref) + end. %%------------------------------------------------------------------------- @@ -802,22 +814,27 @@ api_missuse(doc)-> api_missuse(suite) -> []; api_missuse(Config) when is_list(Config)-> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:call(Ref, {self(), foobar, 10}, infinity), test_server:sleep(10), undefined = process_info(Ref, status), - {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), + odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:cast(Ref2, {self(), foobar, 10}), test_server:sleep(10), undefined = process_info(Ref2, status), - {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), + odbc_test_lib:platform_options()), %% Could be an innocent misstake the connection lives. Ref3 ! foobar, test_server:sleep(10), - {status, _} = process_info(Ref3, status), - ok. + {status, _} = process_info(Ref3, status). +transaction_support_str(mysql) -> + "ENGINE = InnoDB"; +transaction_support_str(_) -> + "". diff --git a/lib/odbc/test/odbc_data_type_SUITE.erl b/lib/odbc/test/odbc_data_type_SUITE.erl index bfb1e4b329..323190dd99 100644 --- a/lib/odbc/test/odbc_data_type_SUITE.erl +++ b/lib/odbc/test/odbc_data_type_SUITE.erl @@ -44,24 +44,29 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> case odbc_test_lib:odbc_check() of ok -> - [{group, char}, {group, int}, {group, floats}, + [{group, char},{group, fixed_char}, {group, binary_char}, + {group, fixed_binary_char}, {group, unicode}, + {group, int}, {group, floats}, {group, dec_and_num}, timestamp]; Other -> {skip, Other} end. groups() -> [{char, [], - [char_fixed_lower_limit, char_fixed_upper_limit, - char_fixed_padding, varchar_lower_limit, + [varchar_lower_limit, varchar_upper_limit, varchar_no_padding, - text_lower_limit, text_upper_limit, unicode]}, + text_lower_limit, text_upper_limit]}, + {fixed_char, [], + [char_fixed_lower_limit, char_fixed_upper_limit, + char_fixed_padding]}, {binary_char, [], - [binary_char_fixed_lower_limit, - binary_char_fixed_upper_limit, - binary_char_fixed_padding, binary_varchar_lower_limit, + [binary_varchar_lower_limit, binary_varchar_upper_limit, binary_varchar_no_padding, - binary_text_lower_limit, binary_text_upper_limit, - unicode]}, + binary_text_lower_limit, binary_text_upper_limit]}, + {fixed_binary_char, [], [binary_char_fixed_lower_limit, + binary_char_fixed_upper_limit, + binary_char_fixed_padding]}, + {unicode, [], [utf8, nchar, nvarchar]}, {int, [], [tiny_int_lower_limit, tiny_int_upper_limit, small_int_lower_limit, small_int_upper_limit, @@ -74,14 +79,33 @@ groups() -> [dec_long, dec_double, dec_bignum, num_long, num_double, num_bignum]}]. +init_per_group(GroupName, Config) when GroupName == fixed_char; + GroupName == fixed_binary_char -> + case ?RDBMS of + mysql -> + {skip, "No supported by MYSQL"}; + _ -> + Config + end; + +init_per_group(unicode, Config) -> + %% Uses parameterized queries + case {os:type(), erlang:system_info(wordsize)} of + {{unix, _}, 4} -> + Config; + {{unix, _}, _} -> + {skip, "Not supported by driver"}; + _ -> + Config + end; + + init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. - - %%-------------------------------------------------------------------- %% Function: init_per_suite(Config) -> Config %% Config - [tuple()] @@ -91,10 +115,18 @@ end_per_group(_GroupName, Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- -init_per_suite(Config) -> - application:start(odbc), - [{tableName, odbc_test_lib:unique_table_name()} | Config]. - +init_per_suite(Config) when is_list(Config) -> + case odbc_test_lib:skip() of + true -> + {skip, "ODBC not supported"}; + false -> + case (catch odbc:start()) of + ok -> + [{tableName, odbc_test_lib:unique_table_name()}| Config]; + _ -> + {skip, "ODBC not startable"} + end + end. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ %% Config - [tuple()] @@ -117,22 +149,81 @@ end_per_suite(_Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- +init_per_testcase(Case, Config) when Case == varchar_upper_limit; + Case == binary_varchar_upper_limit; + Case == varchar_no_padding; + Case == binary_varchar_no_padding -> + case is_fixed_upper_limit(?RDBMS) of + true -> + common_init_per_testcase(Case, Config); + false -> + {skip, "Upper limit is not fixed in" ++ atom_to_list(?RDBMS)} + end; + +init_per_testcase(text_upper_limit, _Config) -> + {skip, "Consumes too much resources"}; + +init_per_testcase(Case, Config) when Case == bit_true; Case == bit_false -> + case is_supported_bit(?RDBMS) of + true -> + common_init_per_testcase(Case, Config); + false -> + {skip, "Not supported by driver"} + end; + +init_per_testcase(param_insert_tiny_int = Case, Config) -> + case is_supported_tinyint(?RDBMS) of + true -> + common_init_per_testcase(Case, Config); + false -> + {skip, "Not supported by driver"} + end; + +init_per_testcase(Case, Config) when Case == nchar; + Case == nvarchar -> + case ?RDBMS of + sqlserver -> + common_init_per_testcase(Case, Config); + _ -> + {skip, "Not supported by driver"} + end; + init_per_testcase(Case, Config) -> + common_init_per_testcase(Case, Config). + +common_init_per_testcase(Case, Config) -> + PlatformOptions = odbc_test_lib:platform_options(), case atom_to_list(Case) of "binary" ++ _ -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{binary_strings, on}]); - "unicode" -> + [{binary_strings, on}] ++ PlatformOptions); + LCase when LCase == "utf8"; + LCase == "nchar"; + LCase == "nvarchar" -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{binary_strings, on}]); + [{binary_strings, on}] ++ PlatformOptions); _ -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []) + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), PlatformOptions) end, + odbc_test_lib:strict(Ref, ?RDBMS), Dog = test_server:timetrap(?default_timeout), Temp = lists:keydelete(connection_ref, 1, Config), NewConfig = lists:keydelete(watchdog, 1, Temp), [{watchdog, Dog}, {connection_ref, Ref} | NewConfig]. +is_fixed_upper_limit(mysql) -> + false; +is_fixed_upper_limit(_) -> + true. +is_supported_tinyint(sqlserver) -> + true; +is_supported_tinyint(_) -> + false. +is_supported_bit(sqlserver) -> + true; +is_supported_bit(_) -> + false. + %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() @@ -146,7 +237,7 @@ end_per_testcase(_TestCase, Config) -> ok = odbc:disconnect(Ref), %% Clean up if needed Table = ?config(tableName, Config), - {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []), + {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), odbc:sql_query(NewRef, "DROP TABLE " ++ Table), odbc:disconnect(NewRef), Dog = ?config(watchdog, Config), @@ -169,18 +260,18 @@ char_fixed_lower_limit(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_fixed_char_table( + ?RDBMS:create_fixed_char_table( (?RDBMS:fixed_char_min() - 1))), %% Lower limit {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_fixed_char_table( - ?RDBMS:fixed_char_min())), + ?RDBMS:create_fixed_char_table( + ?RDBMS:fixed_char_min())), %% Right length data {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, ?RDBMS:fixed_char_min()) + "'" ++ string:chars($a, ?RDBMS:fixed_char_min()) ++ "')"), %% Select data {selected, Fields,[{"a"}]} = @@ -191,11 +282,11 @@ char_fixed_lower_limit(Config) when is_list(Config) -> %% Too long data {error, _} = odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, - (?RDBMS:fixed_char_min() - + 1)) - ++ "')"), - ok. + "'" ++ string:chars($a, + (?RDBMS:fixed_char_min() + + 1)) + ++ "')"). + %%------------------------------------------------------------------------- char_fixed_upper_limit(doc) -> @@ -243,8 +334,7 @@ char_fixed_upper_limit(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ ?RDBMS:create_fixed_char_table( - (?RDBMS:fixed_char_max() + 1))), - ok + (?RDBMS:fixed_char_max() + 1))) end. %%------------------------------------------------------------------------- @@ -261,20 +351,20 @@ char_fixed_padding(Config) when is_list(Config) -> %% Data should be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_fixed_char_table( - ?RDBMS:fixed_char_max())), + ?RDBMS:create_fixed_char_table( + ?RDBMS:fixed_char_max())), - {updated, _} = + {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, - ?RDBMS:fixed_char_min()) + "'" ++ string:chars($a, + ?RDBMS:fixed_char_min()) ++ "')"), {selected, Fields, [{CharStr}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), true = length(CharStr) == ?RDBMS:fixed_char_max(), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). + %%------------------------------------------------------------------------- varchar_lower_limit(doc) -> @@ -287,33 +377,33 @@ varchar_lower_limit(Config) when is_list(Config) -> %% Below limit {error, _} = - odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_var_char_table( - ?RDBMS:var_char_min() - 1)), + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ + ?RDBMS:create_var_char_table( + ?RDBMS:var_char_min() - 1)), %% Lower limit {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_var_char_table( - ?RDBMS:var_char_min())), + ?RDBMS:create_var_char_table( + ?RDBMS:var_char_min())), + + Str = string:chars($a, ?RDBMS:var_char_min()), %% Right length data {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, ?RDBMS:var_char_min()) - ++ "')"), + "'" ++ Str ++ "')"), %% Select data - {selected, Fields, [{"a"}]} = + {selected, Fields, [{Str}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields), - %% Too long data - {error, _} = - odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, - (?RDBMS:var_char_min()+1)) - ++ "')"), - ok. + %% Too long datae + {error, _} = + odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ + "'" ++ string:chars($a, + (?RDBMS:var_char_min()+1)) + ++ "')"). %%------------------------------------------------------------------------- @@ -389,8 +479,7 @@ varchar_no_padding(Config) when is_list(Config) -> {selected, Fields, [{CharStr}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), true = length(CharStr) /= ?RDBMS:var_char_max(), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------- @@ -413,8 +502,7 @@ text_lower_limit(Config) when is_list(Config) -> {selected, Fields, [{"a"}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------- @@ -444,8 +532,7 @@ text_upper_limit(Config) when is_list(Config) -> %% {error, _} = %% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ %% "'" ++ string:chars($a, (?RDBMS:text_max()+1)) -%% ++ "')"), -%% ok. +%% ++ "')"). %%------------------------------------------------------------------------- @@ -469,13 +556,18 @@ binary_char_fixed_lower_limit(Config) when is_list(Config) -> ?RDBMS:create_fixed_char_table( ?RDBMS:fixed_char_min())), + Str = string:chars($a, ?RDBMS:fixed_char_min()), + %% Right length data {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, ?RDBMS:fixed_char_min()) + "'" ++ Str ++ "')"), + + Bin = list_to_binary(Str), + %% Select data - {selected, Fields,[{<<"a">>}]} = + {selected, Fields,[{Bin}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields), @@ -486,8 +578,7 @@ binary_char_fixed_lower_limit(Config) when is_list(Config) -> "'" ++ string:chars($a, (?RDBMS:fixed_char_min() + 1)) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- binary_char_fixed_upper_limit(doc) -> @@ -565,8 +656,8 @@ binary_char_fixed_padding(Config) when is_list(Config) -> {selected, Fields, [{CharBin}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), true = size(CharBin) == ?RDBMS:fixed_char_max(), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). + %%------------------------------------------------------------------------- binary_varchar_lower_limit(doc) -> @@ -588,13 +679,17 @@ binary_varchar_lower_limit(Config) when is_list(Config) -> ?RDBMS:create_var_char_table( ?RDBMS:var_char_min())), + Str = string:chars($a, ?RDBMS:var_char_min()), + %% Right length data {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ string:chars($a, ?RDBMS:var_char_min()) + "'" ++ Str ++ "')"), + BinStr = list_to_binary(Str), + %% Select data - {selected, Fields, [{<<"a">>}]} = + {selected, Fields, [{BinStr}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields), @@ -604,8 +699,7 @@ binary_varchar_lower_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ string:chars($a, (?RDBMS:var_char_min()+1)) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- @@ -654,8 +748,7 @@ binary_varchar_upper_limit(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ ?RDBMS:create_var_char_table( - (?RDBMS:var_char_max() + 1))), - ok + (?RDBMS:var_char_max() + 1))) end. %%------------------------------------------------------------------------- @@ -681,8 +774,7 @@ binary_varchar_no_padding(Config) when is_list(Config) -> {selected, Fields, [{CharBin}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), true = size(CharBin) /= ?RDBMS:var_char_max(), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------- @@ -705,8 +797,7 @@ binary_text_lower_limit(Config) when is_list(Config) -> {selected, Fields, [{<<"a">>}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------- @@ -736,11 +827,7 @@ binary_text_upper_limit(Config) when is_list(Config) -> %% {error, _} = %% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ %% "'" ++ string:chars($a, (?RDBMS:text_max()+1)) -%% ++ "')"), -%% ok. - - -%%------------------------------------------------------------------------- +%% ++ "')"). %%------------------------------------------------------------------------- @@ -774,8 +861,7 @@ tiny_int_lower_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:tiny_int_min() - 1) - ++ "')"), - ok + ++ "')") end. %%------------------------------------------------------------------------- @@ -809,8 +895,7 @@ tiny_int_upper_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:tiny_int_max() + 1) - ++ "')"), - ok + ++ "')") end. %%------------------------------------------------------------------------- @@ -840,8 +925,7 @@ small_int_lower_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:small_int_min() - 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- @@ -870,8 +954,7 @@ small_int_upper_limit(Config) when is_list(Config) -> odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:small_int_max() + 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- int_lower_limit(doc) -> @@ -898,8 +981,7 @@ int_lower_limit(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:int_min() - 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- @@ -927,8 +1009,7 @@ int_upper_limit(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:int_max() + 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- @@ -957,8 +1038,7 @@ big_int_lower_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:big_int_min() - 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- @@ -987,8 +1067,7 @@ big_int_upper_limit(Config) when is_list(Config) -> odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(?RDBMS:big_int_max() + 1) - ++ "')"), - ok. + ++ "')"). %%------------------------------------------------------------------------- bit_false(doc) -> @@ -1020,8 +1099,7 @@ bit_false(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(-1) - ++ "')"), - ok + ++ "')") end. %%------------------------------------------------------------------------- @@ -1056,14 +1134,10 @@ bit_true(Config) when is_list(Config) -> {error, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ "'" ++ integer_to_list(-1) - ++ "')"), - ok + ++ "')") end. %%------------------------------------------------------------------------- - - -%%------------------------------------------------------------------------- float_lower_limit(doc) -> [""]; float_lower_limit(suite) -> @@ -1073,44 +1147,45 @@ float_lower_limit(Config) when is_list(Config) -> Ref = ?config(connection_ref, Config), Table = ?config(tableName, Config), - {updated, _} = % Value == 0 || -1 driver dependent! - odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_float_table()), - - {updated, _} = - odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ float_to_list( - ?RDBMS:float_min()) - ++ "')"), - {selected,[_ColName],[{MinFloat}]} = - odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - - true = ?RDBMS:float_min() == MinFloat, - case ?RDBMS of - oracle -> - {updated, _} = % Value == 0 || -1 driver dependent! - odbc:sql_query(Ref, "DROP TABLE " ++ Table), - + mysql -> + {skip, "Not clearly defined in MYSQL"}; + _ -> {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_float_table()), + ?RDBMS:create_float_table()), {updated, _} = - odbc:sql_query(Ref, - "INSERT INTO " ++ Table ++" VALUES(" ++ - ?RDBMS:float_underflow() ++ ")"), - - SelectResult = ?RDBMS:float_zero_selected(), - SelectResult = - odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table); - _ -> - {error, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - ?RDBMS:float_underflow() ++ ")") - end, - ok. + "'" ++ float_to_list( + ?RDBMS:float_min()) + ++ "')"), + {selected,[_ColName],[{MinFloat}]} = + odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), + true = ?RDBMS:float_min() == MinFloat, + + case ?RDBMS of + oracle -> + {updated, _} = % Value == 0 || -1 driver dependent! + odbc:sql_query(Ref, "DROP TABLE " ++ Table), + + {updated, _} = % Value == 0 || -1 driver dependent! + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ + ?RDBMS:create_float_table()), + {updated, _} = + odbc:sql_query(Ref, + "INSERT INTO " ++ Table ++" VALUES(" ++ + ?RDBMS:float_underflow() ++ ")"), + SelectResult = ?RDBMS:float_zero_selected(), + SelectResult = + odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table); + _ -> + {error, _} = + odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ + ?RDBMS:float_underflow() ++ ")") + end + end. %%------------------------------------------------------------------------- float_upper_limit(doc) -> @@ -1121,26 +1196,28 @@ float_upper_limit(Config) when is_list(Config) -> Ref = ?config(connection_ref, Config), Table = ?config(tableName, Config), - {updated, _} = % Value == 0 || -1 driver dependent! - odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_float_table()), - - {updated, _} = - odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - "'" ++ float_to_list( - ?RDBMS:float_max()) - ++ "')"), - + case ?RDBMS of + mysql -> + {skip, "Not clearly defined in MYSQL"}; + _-> + {updated, _} = % Value == 0 || -1 driver dependent! + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ + ?RDBMS:create_float_table()), - {selected,[_ColName],[{MaxFloat}]} - = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), + {updated, _} = + odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ + "'" ++ float_to_list( + ?RDBMS:float_max()) + ++ "')"), + {selected,[_ColName],[{MaxFloat}]} + = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - true = ?RDBMS:float_max() == MaxFloat, + true = ?RDBMS:float_max() == MaxFloat, - {error, _} = - odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ - ?RDBMS:float_overflow() ++ ")"), - ok. + {error, _} = + odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++ + ?RDBMS:float_overflow() ++ ")") + end. %%------------------------------------------------------------------------- float_zero(doc) -> @@ -1160,8 +1237,7 @@ float_zero(Config) when is_list(Config) -> SelectResult = ?RDBMS:float_zero_selected(), SelectResult = - odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ok. + odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table). %%------------------------------------------------------------------------- real_zero(doc) -> ["Test the real value zero."]; @@ -1185,10 +1261,8 @@ real_zero(Config) when is_list(Config) -> SelectResult = ?RDBMS:real_zero_selected(), SelectResult = - odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ok + odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table) end. -%%------------------------------------------------------------------------- %%------------------------------------------------------------------------ dec_long(doc) -> [""]; @@ -1207,8 +1281,7 @@ dec_long(Config) when is_list(Config) -> {selected, Fields, [{2}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ dec_double(doc) -> [""]; @@ -1255,8 +1328,7 @@ dec_double(Config) when is_list(Config) -> {selected, Fields2, [{1.60000}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields2), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ dec_bignum(doc) -> @@ -1289,8 +1361,7 @@ dec_bignum(Config) when is_list(Config) -> {selected, Fields1, [{"1.6"}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields1), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ num_long(doc) -> [""]; @@ -1309,8 +1380,7 @@ num_long(Config) when is_list(Config) -> {selected, Fields, [{2}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ num_double(doc) -> [""]; @@ -1356,8 +1426,7 @@ num_double(Config) when is_list(Config) -> {selected, Fields2, [{1.6000}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields2), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ num_bignum(doc) -> [""]; @@ -1389,21 +1458,19 @@ num_bignum(Config) when is_list(Config) -> {selected, Fields1, [{"1.6"}]} = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), - ["FIELD"] = odbc_test_lib:to_upper(Fields1), - ok. + ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ -unicode(doc) -> +utf8(doc) -> ["Test unicode support"]; -unicode(suit) -> +utf8(suit) -> []; -unicode(Config) when is_list(Config) -> +utf8(Config) when is_list(Config) -> Ref = ?config(connection_ref, Config), Table = ?config(tableName, Config), - {updated, _} = % Value == 0 || -1 driver dependent! - odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - ?RDBMS:create_unicode_table()), + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ "(FIELD text)"), + Latin1Data = ["���������", "testasdf", "Row 3", @@ -1416,39 +1483,7 @@ unicode(Config) when is_list(Config) -> "Row 10", "Row 11", "Row 12"], - - case ?RDBMS of - sqlserver -> - w_char_support_win(Ref, Table, Latin1Data); - postgres -> - direct_utf8(Ref, Table, Latin1Data); - oracle -> - {skip, "not currently supported"} - end. - -w_char_support_win(Ref, Table, Latin1Data) -> - UnicodeIn = lists:map(fun(S) -> - unicode:characters_to_binary(S,latin1,{utf16,little}) - end, - Latin1Data), - - test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]), - - {updated, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++ "(FIELD) values(?)", - [{{sql_wvarchar,50},UnicodeIn}]), - - {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table), - - test_server:format("UnicodeOut: ~p~n", [UnicodeOut]), - Result = lists:map(fun({Unicode}) -> - unicode:characters_to_list(Unicode,{utf16,little}) - end, - UnicodeOut), - Latin1Data = Result. - - -direct_utf8(Ref, Table, Latin1Data) -> UnicodeIn = lists:map(fun(String) -> unicode:characters_to_binary(String,latin1,utf8) end, @@ -1469,6 +1504,37 @@ direct_utf8(Ref, Table, Latin1Data) -> test_server:format("Result: ~p ~n", [Result]), Latin1Data = Result. +%%------------------------------------------------------------------------ + +nchar(doc) -> + ["Test unicode nchar support in sqlserver"]; +nchar(suit) -> + []; +nchar(Config) when is_list(Config) -> + Ref = ?config(connection_ref, Config), + Table = ?config(tableName, Config), + + {updated, _} = % Value == 0 || -1 driver dependent! + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ + "(FIELD nchar(50))"), + + w_char_support(Ref, Table, sql_wvarchar, 50). + +%%------------------------------------------------------------------------ + +nvarchar(doc) -> + ["Test 'unicode' nvarchar support"]; +nvarchar(suit) -> + []; +nvarchar(Config) when is_list(Config) -> + Ref = ?config(connection_ref, Config), + Table = ?config(tableName, Config), + + {updated, _} = % Value == 0 || -1 driver dependent! + odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ + "(FIELD nvarchar(50))"), + + w_char_support(Ref, Table, sql_wlongvarchar, 50). %%------------------------------------------------------------------------ timestamp(doc) -> @@ -1497,3 +1563,43 @@ timestamp(Config) when is_list(Config) -> TimeStamps = lists:map(fun(Value) -> {Value} end, Data), {selected,_, TimeStamps} = odbc:sql_query(Ref, "SELECT * FROM " ++ Table). +%%------------------------------------------------------------------------ + +w_char_support(Ref, Table, CharType, Size) -> + Latin1Data = ["���������", + "testasdf", + "Row 3", + "Row 4", + "Row 5", + "Row 6", + "Row 7", + "Row 8", + "Row 9", + "Row 10", + "Row 11", + "Row 12"], + + UnicodeIn = lists:map(fun(S) -> + unicode:characters_to_binary(S,latin1,{utf16,little}) + end, + Latin1Data), + + test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]), + + {updated, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++ "(FIELD) values(?)", + [{{CharType, Size},UnicodeIn}]), + + {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table), + + test_server:format("UnicodeOut: ~p~n", [UnicodeOut]), + + PadResult = lists:map(fun({Unicode}) -> + unicode:characters_to_list(Unicode,{utf16,little}) + end, + UnicodeOut), + + test_server:format("Result: ~p~n", [PadResult]), + + Result = lists:map(fun(Str) -> string:strip(Str) end, PadResult), + + Latin1Data = Result. diff --git a/lib/odbc/test/odbc_query_SUITE.erl b/lib/odbc/test/odbc_query_SUITE.erl index 8b8d1e7a40..1852678b4b 100644 --- a/lib/odbc/test/odbc_query_SUITE.erl +++ b/lib/odbc/test/odbc_query_SUITE.erl @@ -43,19 +43,22 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> case odbc_test_lib:odbc_check() of ok -> - [sql_query, first, last, next, prev, select_count, + [sql_query, next, {group, scrollable_cursors}, select_count, select_next, select_relative, select_absolute, create_table_twice, delete_table_twice, duplicate_key, not_connection_owner, no_result_set, query_error, - multiple_select_result_sets, multiple_mix_result_sets, - multiple_result_sets_error, + {group, multiple_result_sets}, {group, parameterized_queries}, {group, describe_table}, delete_nonexisting_row]; Other -> {skip, Other} end. groups() -> - [{parameterized_queries, [], + [{multiple_result_sets, [], [multiple_select_result_sets, + multiple_mix_result_sets, + multiple_result_sets_error]}, + {scrollable_cursors, [], [first, last, prev]}, + {parameterized_queries, [], [{group, param_integers}, param_insert_decimal, param_insert_numeric, {group, param_insert_string}, param_insert_float, param_insert_real, @@ -72,15 +75,27 @@ groups() -> [describe_integer, describe_string, describe_floating, describe_dec_num, describe_no_such_table]}]. -init_per_group(_GroupName, Config) -> +init_per_group(multiple_result_sets, Config) -> + case is_supported_multiple_resultsets(?RDBMS) of + true -> + Config; + false -> + {skip, "Not supported by " ++ atom_to_list(?RDBMS) ++ "driver"} + end; +init_per_group(scrollable_cursors, Config) -> + case proplists:get_value(scrollable_cursors, odbc_test_lib:platform_options()) of + off -> + {skip, "Not supported by driver"}; + _ -> + Config + end; + +init_per_group(_,Config) -> Config. end_per_group(_GroupName, Config) -> Config. - - - %%-------------------------------------------------------------------- %% Function: init_per_suite(Config) -> Config %% Config - [tuple()] @@ -91,8 +106,17 @@ end_per_group(_GroupName, Config) -> %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) when is_list(Config) -> - application:start(odbc), - [{tableName, odbc_test_lib:unique_table_name()}| Config]. + case odbc_test_lib:skip() of + true -> + {skip, "ODBC not supported"}; + false -> + case (catch odbc:start()) of + ok -> + [{tableName, odbc_test_lib:unique_table_name()}| Config]; + _ -> + {skip, "ODBC not startable"} + end + end. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ @@ -117,7 +141,8 @@ end_per_suite(_Config) -> %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_testcase(_Case, Config) -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []), + {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), + odbc_test_lib:strict(Ref, ?RDBMS), Dog = test_server:timetrap(?default_timeout), Temp = lists:keydelete(connection_ref, 1, Config), NewConfig = lists:keydelete(watchdog, 1, Temp), @@ -136,7 +161,7 @@ end_per_testcase(_Case, Config) -> ok = odbc:disconnect(Ref), %% Clean up if needed Table = ?config(tableName, Config), - {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []), + {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), odbc:sql_query(NewRef, "DROP TABLE " ++ Table), odbc:disconnect(NewRef), Dog = ?config(watchdog, Config), @@ -663,9 +688,6 @@ multiple_result_sets_error(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- - -%%------------------------------------------------------------------------- -%%------------------------------------------------------------------------- param_insert_tiny_int(doc)-> ["Test insertion of tiny ints by parameterized queries."]; param_insert_tiny_int(suite) -> @@ -901,8 +923,6 @@ param_insert_numeric(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- - -%%------------------------------------------------------------------------- param_insert_char(doc)-> ["Test insertion of fixed length string by parameterized queries."]; param_insert_char(suite) -> @@ -1325,8 +1345,6 @@ param_select(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- - -%%------------------------------------------------------------------------- describe_integer(doc) -> ["Test describe_table/[2,3] for integer columns."]; describe_integer(suite) -> @@ -1338,7 +1356,7 @@ describe_integer(Config) when is_list(Config) -> {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (int1 SMALLINT, int2 INT, int3 INTEGER)"), + " (myint1 SMALLINT, myint2 INT, myint3 INTEGER)"), Decs = ?RDBMS:describe_integer(), %% Make sure to test timeout clause @@ -1399,7 +1417,7 @@ describe_dec_num(Config) when is_list(Config) -> {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ - " (dec DECIMAL(9,3), num NUMERIC(9,2))"), + " (mydec DECIMAL(9,3), mynum NUMERIC(9,2))"), Decs = ?RDBMS:describe_dec_num(), @@ -1451,3 +1469,7 @@ is_driver_error(Error) -> false -> test_server:fail(Error) end. +is_supported_multiple_resultsets(sqlserver) -> + true; +is_supported_multiple_resultsets(_) -> + false. diff --git a/lib/odbc/test/odbc_start_SUITE.erl b/lib/odbc/test/odbc_start_SUITE.erl index 65b990133f..e3a3440559 100644 --- a/lib/odbc/test/odbc_start_SUITE.erl +++ b/lib/odbc/test/odbc_start_SUITE.erl @@ -39,11 +39,18 @@ %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> - case code:which(odbc) of - non_existing -> - {skip, "No ODBC built"}; - _ -> - [{tableName, odbc_test_lib:unique_table_name()} | Config] + case odbc_test_lib:skip() of + true -> + {skip, "ODBC not supported"}; + false -> + case code:which(odbc) of + non_existing -> + {skip, "No ODBC built"}; + _ -> + %% Make sure odbc is not already started + odbc:stop(), + [{tableName, odbc_test_lib:unique_table_name()} | Config] + end end. %%-------------------------------------------------------------------- @@ -125,14 +132,16 @@ start(doc) -> start(suite) -> []; start(Config) when is_list(Config) -> - {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), []), + PlatformOptions = odbc_test_lib:platform_options(), + {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), + PlatformOptions), odbc:start(), - case odbc:connect(?RDBMS:connection_string(), []) of + case odbc:connect(?RDBMS:connection_string(), PlatformOptions) of {ok, Ref0} -> ok = odbc:disconnect(Ref0), odbc:stop(), {error,odbc_not_started} = - odbc:connect(?RDBMS:connection_string(), []), + odbc:connect(?RDBMS:connection_string(), PlatformOptions), start_odbc(transient), start_odbc(permanent); {error, odbc_not_started} -> @@ -144,7 +153,7 @@ start(Config) when is_list(Config) -> start_odbc(Type) -> ok = odbc:start(Type), - case odbc:connect(?RDBMS:connection_string(), []) of + case odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()) of {ok, Ref} -> ok = odbc:disconnect(Ref), odbc:stop(); diff --git a/lib/odbc/test/odbc_test.hrl b/lib/odbc/test/odbc_test.hrl index 87f50043db..f7bb338a7f 100644 --- a/lib/odbc/test/odbc_test.hrl +++ b/lib/odbc/test/odbc_test.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -25,9 +25,16 @@ -define(RDBMS, case os:type() of {unix, sunos} -> - postgres; + mysql; {unix,linux} -> - postgres; + case erlang:system_info({wordsize, external}) of + 4 -> + mysql; + _ -> + postgres + end; + {unix, darwin} -> + mysql; {win32, _} -> sqlserver end). diff --git a/lib/odbc/test/odbc_test_lib.erl b/lib/odbc/test/odbc_test_lib.erl index 012eb96e43..2d6bf5fcac 100644 --- a/lib/odbc/test/odbc_test_lib.erl +++ b/lib/odbc/test/odbc_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -36,20 +36,9 @@ match_float(Float, Match, Delta) -> (Float < Match + Delta) and (Float > Match - Delta). odbc_check() -> - case erlang:system_info(wordsize) of + case erlang:system_info({wordsize, external}) of 4 -> - case test_server:os_type() of - {unix, sunos} -> - ok; - {unix, linux} -> - ok; - {win32, _} -> - ok; - Other -> - lists:flatten( - io_lib:format("Platform not supported: ~w", - [Other])) - end; + ok; Other -> case os:type() of {unix, linux} -> @@ -75,3 +64,43 @@ check_row_count(Expected, Count) -> to_upper(List) -> lists:map(fun(Str) -> string:to_upper(Str) end, List). + +strict(Ref, mysql) -> + odbc:sql_query(Ref, "SET sql_mode='STRICT_ALL_TABLES,STRICT_TRANS_TABLES';"); +strict(_,_) -> + ok. + +platform_options() -> + []. + +skip() -> + case os:type() of + {unix, linux} -> + Issue = linux_issue(), + is_sles9(Issue); + {unix, sunos} -> + not supported_solaris(); + _ -> + false + end. + +supported_solaris() -> + case os:version() of + {_,10,_} -> + true; + _ -> + false + end. + +linux_issue() -> + {ok, Binary} = file:read_file("/etc/issue"), + string:tokens(binary_to_list(Binary), " "). + +is_sles11(IssueTokens) -> + lists:member(11, IssueTokens). + +is_sles10(IssueTokens) -> + lists:member(10, IssueTokens). + +is_sles9(IssueTokens) -> + lists:member(9, IssueTokens). diff --git a/lib/odbc/test/oracle.erl b/lib/odbc/test/oracle.erl index ebf6dbb6bf..d74863d8c1 100644 --- a/lib/odbc/test/oracle.erl +++ b/lib/odbc/test/oracle.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -108,10 +108,6 @@ create_text_table() -> " (FIELD long)". %Oracle long is variable length char data %------------------------------------------------------------------------- -create_unicode_table() -> - " (FIELD nvarchar(50))". - -%------------------------------------------------------------------------- create_timestamp_table() -> " (FIELD DATETIME)". @@ -231,8 +227,8 @@ param_select() -> %------------------------------------------------------------------------- describe_integer() -> - {ok,[{"INT1",{sql_decimal,38,0}},{"INT2",{sql_decimal,38,0}}, - {"INT3",{sql_decimal,38,0}}]}. + {ok,[{"MYINT1",{sql_decimal,38,0}},{"MYINT2",{sql_decimal,38,0}}, + {"MYINT3",{sql_decimal,38,0}}]}. describe_string() -> {ok,[{"STR1",{sql_char,10}}, @@ -243,4 +239,4 @@ describe_string() -> describe_floating() -> {ok,[{"F",sql_double},{"R",sql_double},{"D",sql_double}]}. describe_dec_num() -> - {ok,[{"DEC",{sql_decimal,9,3}},{"NUM",{sql_decimal,9,2}}]}. + {ok,[{"MYDEC",{sql_decimal,9,3}},{"MYNUM",{sql_decimal,9,2}}]}. diff --git a/lib/odbc/test/postgres.erl b/lib/odbc/test/postgres.erl index 169ca26e43..d564dbd5ff 100644 --- a/lib/odbc/test/postgres.erl +++ b/lib/odbc/test/postgres.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. 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 @@ -30,7 +30,7 @@ connection_string() -> {unix, sunos} -> "DSN=Postgres;UID=odbctest"; {unix, linux} -> - Size = erlang:system_info(wordsize), + Size = erlang:system_info({wordsize, external}), linux_dist_connection_string(Size) end. @@ -43,7 +43,12 @@ linux_dist_connection_string(4) -> end; linux_dist_connection_string(_) -> - "DSN=PostgresLinux64;UID=odbctest". + case linux_dist() of + "ubuntu" -> + "DSN=PostgresLinux64Ubuntu;UID=odbctest"; + _ -> + "DSN=PostgresLinux64;UID=odbctest" + end. linux_dist() -> case file:read_file("/etc/issue") of @@ -135,10 +140,6 @@ create_text_table() -> " (FIELD text)". %------------------------------------------------------------------------- -create_unicode_table() -> - " (FIELD text)". - -%------------------------------------------------------------------------- create_timestamp_table() -> " (FIELD TIMESTAMP)". @@ -275,9 +276,9 @@ param_select() -> %------------------------------------------------------------------------- describe_integer() -> - {ok,[{"int1",sql_smallint}, - {"int2",sql_integer}, - {"int3",sql_integer}]}. + {ok,[{"myint1",sql_smallint}, + {"myint2",sql_integer}, + {"myint3",sql_integer}]}. describe_string() -> {ok,[{"str1",{sql_char,10}}, @@ -288,7 +289,7 @@ describe_string() -> describe_floating() -> {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,15}}]}. describe_dec_num() -> - {ok,[{"dec",{sql_numeric,9,3}},{"num",{sql_numeric,9,2}}]}. + {ok,[{"mydec",{sql_numeric,9,3}},{"mynum",{sql_numeric,9,2}}]}. describe_timestamp() -> {ok, [{"field", sql_timestamp}]}. diff --git a/lib/odbc/test/sqlserver.erl b/lib/odbc/test/sqlserver.erl index e3fe30e0bc..59252d4276 100644 --- a/lib/odbc/test/sqlserver.erl +++ b/lib/odbc/test/sqlserver.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -123,10 +123,6 @@ create_text_table() -> " (FIELD text)". %------------------------------------------------------------------------- -create_unicode_table() -> - " (FIELD nvarchar(50))". - -%------------------------------------------------------------------------- create_timestamp_table() -> " (FIELD DATETIME)". @@ -279,8 +275,8 @@ param_select() -> %------------------------------------------------------------------------- describe_integer() -> - {ok,[{"int1", sql_smallint},{"int2", sql_integer}, - {"int3", sql_integer}]}. + {ok,[{"myint1", sql_smallint},{"myint2", sql_integer}, + {"myint3", sql_integer}]}. describe_string() -> {ok,[{"str1",{sql_char,10}}, @@ -292,7 +288,7 @@ describe_floating() -> {ok,[{"f", sql_real},{"r", sql_real}, {"d", {sql_float, 53}}]}. describe_dec_num() -> - {ok,[{"dec",{sql_decimal,9,3}},{"num",{sql_numeric,9,2}}]}. + {ok,[{"mydec",{sql_decimal,9,3}},{"mynum",{sql_numeric,9,2}}]}. describe_timestamp() -> {ok, [{"field", sql_timestamp}]}. diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk index 42a51be33e..120ed9ee3d 100644 --- a/lib/odbc/vsn.mk +++ b/lib/odbc/vsn.mk @@ -1 +1 @@ -ODBC_VSN = 2.10.10 +ODBC_VSN = 2.10.11 diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml index 12abfd244f..1fa426a79e 100644 --- a/lib/parsetools/doc/src/leex.xml +++ b/lib/parsetools/doc/src/leex.xml @@ -79,6 +79,10 @@ Token = tuple()</code> <item><p>Causes warnings to be printed as they occur. Default is <c>true</c>.</p> </item> + <tag><c>warnings_as_errors</c></tag> + <item> + <p>Causes warnings to be treated as errors.</p> + </item> <tag><c>{report, bool()}</c></tag> <item><p>This is a short form for both <c>report_errors</c> and <c>report_warnings</c>.</p> diff --git a/lib/parsetools/doc/src/yecc.xml b/lib/parsetools/doc/src/yecc.xml index 81f1550b0a..1d2a985d7d 100644 --- a/lib/parsetools/doc/src/yecc.xml +++ b/lib/parsetools/doc/src/yecc.xml @@ -95,6 +95,10 @@ <item>This is a short form for both <c>report_errors</c> and <c>report_warnings</c>. </item> + <tag><c>warnings_as_errors</c></tag> + <item> + <p>Causes warnings to be treated as errors.</p> + </item> <tag><c>{return_errors, bool()}</c>.</tag> <item>If this flag is set, <c>{error, Errors, Warnings}</c> is returned when there are errors. Default is @@ -421,9 +425,9 @@ myparser:parse_and_scan({Mod, Tokenizer, Args}) </code> Nonterminals E T F. Terminals '+' '*' '(' ')' number. Rootsymbol E. -E -> E '+' T: ['$1', '$2', '$3']. +E -> E '+' T: ['$2', '$1', '$3']. E -> T : '$1'. -T -> T '*' F: ['$1', '$2', '$3']. +T -> T '*' F: ['$2', '$1', '$3']. T -> F : '$1'. F -> '(' E ')' : '$2'. F -> number : '$1'. </code> @@ -434,8 +438,8 @@ Terminals '+' '*' '(' ')' number. Rootsymbol E. Left 100 '+'. Left 200 '*'. -E -> E '+' E : ['$1', '$2', '$3']. -E -> E '*' E : ['$1', '$2', '$3']. +E -> E '+' E : ['$2', '$1', '$3']. +E -> E '*' E : ['$2', '$1', '$3']. E -> '(' E ')' : '$2'. E -> number : '$1'. </code> <p>3. An overloaded minus operator:</p> diff --git a/lib/parsetools/src/leex.erl b/lib/parsetools/src/leex.erl index d0b4b9efe7..942e9928b1 100644 --- a/lib/parsetools/src/leex.erl +++ b/lib/parsetools/src/leex.erl @@ -35,7 +35,7 @@ -export([compile/3,file/1,file/2,format_error/1]). -import(lists, [member/2,reverse/1,sort/1,delete/2, - keysort/2,keydelete/3,keyfind/3, + keysort/2,keydelete/3, map/2,foldl/3,foreach/2,flatmap/2]). -import(string, [substr/2,substr/3,span/2]). -import(ordsets, [is_element/2,add_element/2,union/2]). @@ -58,7 +58,7 @@ gfile=[], % Graph file module, % Module name opts=[], % Options - posix=false, % POSIX regular expressions + % posix=false, % POSIX regular expressions errors=[], warnings=[] }). @@ -107,10 +107,15 @@ file(File, Opts0) -> St = try {ok,REAs,Actions,Code,St2} = parse_file(St1), {DFA,DF} = make_dfa(REAs, St2), - St3 = out_file(St2, DFA, DF, Actions, Code), - case lists:member(dfa_graph, St3#leex.opts) of - true -> out_dfa_graph(St3, DFA, DF); - false -> St3 + case werr(St2) of + false -> + St3 = out_file(St2, DFA, DF, Actions, Code), + case lists:member(dfa_graph, St3#leex.opts) of + true -> out_dfa_graph(St3, DFA, DF); + false -> St3 + end; + true -> + St2 end catch #leex{}=St4 -> St4 @@ -131,8 +136,8 @@ format_error({regexp,E})-> "unterminated " ++ Cs; {illegal_char,Cs} -> "illegal character " ++ Cs; - {posix_cc,What} -> - ["illegal POSIX character class ",io_lib:write_string(What)]; +%% {posix_cc,What} -> +%% ["illegal POSIX character class ",io_lib:write_string(What)]; {char_class,What} -> ["illegal character class ",io_lib:write_string(What)] end, @@ -163,7 +168,8 @@ options(Options0) when is_list(Options0) -> (T) -> [T] end, Options0), options(Options, [scannerfile,includefile,report_errors, - report_warnings,return_errors,return_warnings, + report_warnings,warnings_as_errors, + return_errors,return_warnings, verbose,dfa_graph], []) catch error: _ -> badarg end; @@ -217,6 +223,7 @@ default_option(dfa_graph) -> false; default_option(includefile) -> []; default_option(report_errors) -> true; default_option(report_warnings) -> true; +default_option(warnings_as_errors) -> false; default_option(return_errors) -> false; default_option(return_warnings) -> false; default_option(scannerfile) -> []; @@ -225,6 +232,7 @@ default_option(verbose) -> false. atom_option(dfa_graph) -> {dfa_graph,true}; atom_option(report_errors) -> {report_errors,true}; atom_option(report_warnings) -> {report_warnings,true}; +atom_option(warnings_as_errors) -> {warnings_as_errors,true}; atom_option(return_errors) -> {return_errors,true}; atom_option(return_warnings) -> {return_warnings,true}; atom_option(verbose) -> {verbose,true}; @@ -251,19 +259,29 @@ leex_ret(St) -> report_warnings(St), Es = pack_errors(St#leex.errors), Ws = pack_warnings(St#leex.warnings), + Werr = werr(St), if + Werr -> + do_error_return(St, Es, Ws); Es =:= [] -> case member(return_warnings, St#leex.opts) of true -> {ok, St#leex.efile, Ws}; false -> {ok, St#leex.efile} end; - true -> - case member(return_errors, St#leex.opts) of - true -> {error, Es, Ws}; - false -> error - end + true -> + do_error_return(St, Es, Ws) end. +do_error_return(St, Es, Ws) -> + case member(return_errors, St#leex.opts) of + true -> {error, Es, Ws}; + false -> error + end. + +werr(St) -> + member(warnings_as_errors, St#leex.opts) + andalso length(St#leex.warnings) > 0. + pack_errors([{File,_} | _] = Es) -> [{File, flatmap(fun({_,E}) -> [E] end, sort(Es))}]; pack_errors([]) -> @@ -296,6 +314,7 @@ report_warnings(St) -> end, sort(St#leex.warnings)) end, report_warnings, St#leex.opts). +-spec add_error(_, #leex{}) -> no_return(). add_error(E, St) -> add_error(St#leex.xfile, E, St). @@ -644,14 +663,14 @@ re_repeat1([$*|Cs], Sn, S, St) -> re_repeat1(Cs, Sn, {kclosure,S}, St); re_repeat1([$+|Cs], Sn, S, St) -> re_repeat1(Cs, Sn, {pclosure,S}, St); re_repeat1([$?|Cs], Sn, S, St) -> re_repeat1(Cs, Sn, {optional,S}, St); %% { only starts interval when ere is true, otherwise normal character. -re_repeat1([${|Cs0], Sn, S, #leex{posix=true}=St) -> % $} - case re_interval_range(Cs0) of - {Min,Max,[$}|Cs1]} when is_integer(Min), is_integer(Max), Min =< Max -> - re_repeat1(Cs1, Sn, {interval,S,Min,Max}, St); - {Min,Max,[$}|Cs1]} when is_integer(Min), is_atom(Max) -> - re_repeat1(Cs1, Sn, {interval,S,Min,Max}, St); - {_,_,Cs1} -> parse_error({interval_range,string_between([${|Cs0], Cs1)}) - end; +%% re_repeat1([${|Cs0], Sn, S, #leex{posix=true}=St) -> % $} +%% case re_interval_range(Cs0) of +%% {Min,Max,[$}|Cs1]} when is_integer(Min), is_integer(Max), Min =< Max -> +%% re_repeat1(Cs1, Sn, {interval,S,Min,Max}, St); +%% {Min,Max,[$}|Cs1]} when is_integer(Min), is_atom(Max) -> +%% re_repeat1(Cs1, Sn, {interval,S,Min,Max}, St); +%% {_,_,Cs1} -> parse_error({interval_range,string_between([${|Cs0], Cs1)}) +%% end; re_repeat1(Cs, Sn, S, _) -> {S,Sn,Cs}. %% re_single(Chars, SubNumber, State) -> {RegExp,SubNumber,Chars}. @@ -733,7 +752,7 @@ special_char($|, _) -> true; special_char($*, _) -> true; special_char($+, _) -> true; special_char($?, _) -> true; -special_char(${, #leex{posix=true}) -> true; % Only when POSIX set +%% special_char(${, #leex{posix=true}) -> true; % Only when POSIX set special_char($\\, _) -> true; special_char(_, _) -> false. @@ -744,12 +763,12 @@ re_char_class([$]|Cs], St) -> % Must special case this. re_char_class(Cs, [$]], St); re_char_class(Cs, St) -> re_char_class(Cs, [], St). -re_char_class("[:" ++ Cs0, Cc, #leex{posix=true}=St) -> - %% POSIX char class only. - case posix_cc(Cs0) of - {Pcl,":]" ++ Cs1} -> re_char_class(Cs1, [{posix,Pcl}|Cc], St); - {_,Cs1} -> parse_error({posix_cc,string_between(Cs0, Cs1)}) - end; +%% re_char_class("[:" ++ Cs0, Cc, #leex{posix=true}=St) -> +%% %% POSIX char class only. +%% case posix_cc(Cs0) of +%% {Pcl,":]" ++ Cs1} -> re_char_class(Cs1, [{posix,Pcl}|Cc], St); +%% {_,Cs1} -> parse_error({posix_cc,string_between(Cs0, Cs1)}) +%% end; re_char_class([C1|Cs0], Cc, St) when C1 =/= $] -> case re_char(C1, Cs0) of {Cf,[$-,C2|Cs1]} when C2 =/= $] -> @@ -766,19 +785,19 @@ re_char_class(Cs, Cc, _) -> {reverse(Cc),Cs}. % Preserve order %% posix_cc(String) -> {PosixClass,RestString}. %% Handle POSIX character classes. -posix_cc("alnum" ++ Cs) -> {alnum,Cs}; -posix_cc("alpha" ++ Cs) -> {alpha,Cs}; -posix_cc("blank" ++ Cs) -> {blank,Cs}; -posix_cc("cntrl" ++ Cs) -> {cntrl,Cs}; -posix_cc("digit" ++ Cs) -> {digit,Cs}; -posix_cc("graph" ++ Cs) -> {graph,Cs}; -posix_cc("lower" ++ Cs) -> {lower,Cs}; -posix_cc("print" ++ Cs) -> {print,Cs}; -posix_cc("punct" ++ Cs) -> {punct,Cs}; -posix_cc("space" ++ Cs) -> {space,Cs}; -posix_cc("upper" ++ Cs) -> {upper,Cs}; -posix_cc("xdigit" ++ Cs) -> {xdigit,Cs}; -posix_cc(Cs) -> parse_error({posix_cc,substr(Cs, 1, 5)}). +%% posix_cc("alnum" ++ Cs) -> {alnum,Cs}; +%% posix_cc("alpha" ++ Cs) -> {alpha,Cs}; +%% posix_cc("blank" ++ Cs) -> {blank,Cs}; +%% posix_cc("cntrl" ++ Cs) -> {cntrl,Cs}; +%% posix_cc("digit" ++ Cs) -> {digit,Cs}; +%% posix_cc("graph" ++ Cs) -> {graph,Cs}; +%% posix_cc("lower" ++ Cs) -> {lower,Cs}; +%% posix_cc("print" ++ Cs) -> {print,Cs}; +%% posix_cc("punct" ++ Cs) -> {punct,Cs}; +%% posix_cc("space" ++ Cs) -> {space,Cs}; +%% posix_cc("upper" ++ Cs) -> {upper,Cs}; +%% posix_cc("xdigit" ++ Cs) -> {xdigit,Cs}; +%% posix_cc(Cs) -> parse_error({posix_cc,substr(Cs, 1, 5)}). escape_char($n) -> $\n; % \n = LF escape_char($r) -> $\r; % \r = CR @@ -797,24 +816,24 @@ escape_char(C) -> C. % Pass it straight through %% Int, -> Int,any %% Int1,Int2 -> Int1,Int2 -re_interval_range(Cs0) -> - case re_number(Cs0) of - {none,Cs1} -> {none,none,Cs1}; - {N,[$,|Cs1]} -> - case re_number(Cs1) of - {none,Cs2} -> {N,any,Cs2}; - {M,Cs2} -> {N,M,Cs2} - end; - {N,Cs1} -> {N,none,Cs1} - end. +%% re_interval_range(Cs0) -> +%% case re_number(Cs0) of +%% {none,Cs1} -> {none,none,Cs1}; +%% {N,[$,|Cs1]} -> +%% case re_number(Cs1) of +%% {none,Cs2} -> {N,any,Cs2}; +%% {M,Cs2} -> {N,M,Cs2} +%% end; +%% {N,Cs1} -> {N,none,Cs1} +%% end. -re_number([C|Cs]) when C >= $0, C =< $9 -> - re_number(Cs, C - $0); -re_number(Cs) -> {none,Cs}. +%% re_number([C|Cs]) when C >= $0, C =< $9 -> +%% re_number(Cs, C - $0); +%% re_number(Cs) -> {none,Cs}. -re_number([C|Cs], Acc) when C >= $0, C =< $9 -> - re_number(Cs, 10*Acc + (C - $0)); -re_number(Cs, Acc) -> {Acc,Cs}. +%% re_number([C|Cs], Acc) when C >= $0, C =< $9 -> +%% re_number(Cs, 10*Acc + (C - $0)); +%% re_number(Cs, Acc) -> {Acc,Cs}. string_between(Cs1, Cs2) -> substr(Cs1, 1, length(Cs1)-length(Cs2)). diff --git a/lib/parsetools/src/yecc.erl b/lib/parsetools/src/yecc.erl index 4119e2631b..72cff3af92 100644 --- a/lib/parsetools/src/yecc.erl +++ b/lib/parsetools/src/yecc.erl @@ -278,8 +278,8 @@ options(Options0) when is_list(Options0) -> (T) -> [T] end, Options0), options(Options, [file_attributes, includefile, parserfile, - report_errors, report_warnings, return_errors, - return_warnings, time, verbose], []) + report_errors, report_warnings, warnings_as_errors, + return_errors, return_warnings, time, verbose], []) catch error: _ -> badarg end; options(Option) -> @@ -333,6 +333,7 @@ default_option(includefile) -> []; default_option(parserfile) -> []; default_option(report_errors) -> true; default_option(report_warnings) -> true; +default_option(warnings_as_errors) -> false; default_option(return_errors) -> false; default_option(return_warnings) -> false; default_option(time) -> false; @@ -341,6 +342,7 @@ default_option(verbose) -> false. atom_option(file_attributes) -> {file_attributes, true}; atom_option(report_errors) -> {report_errors, true}; atom_option(report_warnings) -> {report_warnings, true}; +atom_option(warnings_as_errors) -> {warnings_as_errors,true}; atom_option(return_errors) -> {return_errors, true}; atom_option(return_warnings) -> {return_warnings, true}; atom_option(time) -> {time, true}; @@ -409,12 +411,16 @@ infile(Parent, Infilex, Options) -> {error, Reason} -> add_error(St0#yecc.infile, none, {file_error, Reason}, St0) end, - case St#yecc.errors of - [] -> ok; + case {St#yecc.errors, werr(St)} of + {[], false} -> ok; _ -> _ = file:delete(St#yecc.outfile) end, Parent ! {self(), yecc_ret(St)}. +werr(St) -> + member(warnings_as_errors, St#yecc.options) + andalso length(St#yecc.warnings) > 0. + outfile(St0) -> case file:open(St0#yecc.outfile, [write, delayed_write]) of {ok, Outport} -> @@ -777,17 +783,23 @@ yecc_ret(St0) -> report_warnings(St), Es = pack_errors(St#yecc.errors), Ws = pack_warnings(St#yecc.warnings), + Werr = werr(St), if + Werr -> + do_error_return(St, Es, Ws); Es =:= [] -> case member(return_warnings, St#yecc.options) of true -> {ok, St#yecc.outfile, Ws}; false -> {ok, St#yecc.outfile} end; true -> - case member(return_errors, St#yecc.options) of - true -> {error, Es, Ws}; - false -> error - end + do_error_return(St, Es, Ws) + end. + +do_error_return(St, Es, Ws) -> + case member(return_errors, St#yecc.options) of + true -> {error, Es, Ws}; + false -> error end. check_expected(St0) -> diff --git a/lib/parsetools/test/leex_SUITE.erl b/lib/parsetools/test/leex_SUITE.erl index 23ad16f98d..48312445ef 100644 --- a/lib/parsetools/test/leex_SUITE.erl +++ b/lib/parsetools/test/leex_SUITE.erl @@ -152,6 +152,19 @@ file(Config) when is_list(Config) -> ?line writable(Dotfile), file:delete(Dotfile), + Warn = <<"Definitions.1998\n" + "D = [0-9]\n" + "Rules.\n" + "{L}+ : {token,{word,TokenLine,TokenChars}}.\n" + "Erlang code.\n">>, + ok = file:write_file(Filename, Warn), + error = leex:file(Filename, [warnings_as_errors]), + error = leex:file(Filename, [return_warnings,warnings_as_errors]), + {ok,Scannerfile,[{Filename,[{1,leex,ignored_characters}]}]} = + leex:file(Filename, [return_warnings]), + {error,_,[{Filename,[{1,leex,ignored_characters}]}]} = + leex:file(Filename, [return_errors,warnings_as_errors]), + file:delete(Filename), ok. @@ -551,7 +564,7 @@ ex2(Config) when is_list(Config) -> <<" %%% File : erlang_scan.xrl %%% Author : Robert Virding -%%% Purpose : Tkoen definitions for Erlang. +%%% Purpose : Token definitions for Erlang. Definitions. O = [0-7] diff --git a/lib/parsetools/test/yecc_SUITE.erl b/lib/parsetools/test/yecc_SUITE.erl index 1de87b3bff..0133524950 100644 --- a/lib/parsetools/test/yecc_SUITE.erl +++ b/lib/parsetools/test/yecc_SUITE.erl @@ -247,6 +247,14 @@ syntax(Config) when is_list(Config) -> ?line {ok,_,[{_,[{2,yecc,bad_declaration}]}]} = yecc:file(Filename, Ret), + %% Bad declaration with warnings_as_errors. + error = yecc:file(Filename, [warnings_as_errors]), + error = yecc:file(Filename, [return_warnings,warnings_as_errors]), + {ok,_,[{_,[{2,yecc,bad_declaration}]}]} = + yecc:file(Filename, [return_warnings]), + {error,_,[{_,[{2,yecc,bad_declaration}]}]} = + yecc:file(Filename, [return_errors,warnings_as_errors]), + %% Bad declaration. ?line ok = file:write_file(Filename, <<"Nonterminals nt. Terminals t. diff --git a/lib/percept/src/percept_db.erl b/lib/percept/src/percept_db.erl index 52e9afb78f..61b68ce44f 100644 --- a/lib/percept/src/percept_db.erl +++ b/lib/percept/src/percept_db.erl @@ -92,7 +92,7 @@ restart(PerceptDB)-> stop_sync(PerceptDB), do_start(). -%% @spec do_start(pid()) -> pid() +%% @spec do_start() -> pid() %% @private %% @doc starts the percept database. @@ -131,6 +131,7 @@ stop_sync(Pid)-> {'DOWN', MonitorRef, _Type, Pid, _Info}-> true after ?STOP_TIMEOUT-> + erlang:demonitor(MonitorRef, [flush]), exit(Pid, kill) end. @@ -166,14 +167,14 @@ insert(Trace) -> select(Query) -> percept_db ! {select, self(), Query}, - receive Match -> Match end. + receive {result, Match} -> Match end. %% @spec select(atom(), list()) -> Result %% @equiv select({Table,Options}) select(Table, Options) -> percept_db ! {select, self(), {Table, Options}}, - receive Match -> Match end. + receive {result, Match} -> Match end. %% @spec consolidate() -> Result %% @doc Checks timestamp and state-flow inconsistencies in the @@ -213,7 +214,7 @@ loop_percept_db() -> insert_trace(clean_trace(Trace)), loop_percept_db(); {select, Pid, Query} -> - Pid ! select_query(Query), + Pid ! {result, select_query(Query)}, loop_percept_db(); {action, stop} -> stopped; @@ -222,7 +223,7 @@ loop_percept_db() -> loop_percept_db(); {operate, Pid, {Table, {Fun, Start}}} -> Result = ets:foldl(Fun, Start, Table), - Pid ! Result, + Pid ! {result, Result}, loop_percept_db(); Unhandled -> io:format("loop_percept_db, unhandled query: ~p~n", [Unhandled]), diff --git a/lib/public_key/asn1/README b/lib/public_key/asn1/README index 5fb8cf9725..2a880e2d51 100644 --- a/lib/public_key/asn1/README +++ b/lib/public_key/asn1/README @@ -46,6 +46,6 @@ diff -r1.1 PKIXAttributeCertificate.asn1 --- > version AttCertVersion, -- version is v2 -4. Defenitions of publuic keys from PKCS-1.asn1 present in +4. Definitions of public keys from PKCS-1.asn1 present in PKIX1Algorithms88.asn1 where removed as we take them directly from PKCS-1.asn1
\ No newline at end of file diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index d60d91cd83..9a3832c68b 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -63,7 +63,7 @@ <p><code>pki_asn1_type() = 'Certificate' | 'RSAPrivateKey'| 'RSAPublicKey' 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' | 'SubjectPublicKeyInfo'</code></p> - <p><code>pem_entry () = {pki_asn1_type(), binary() %% DER or encrypted DER + <p><code>pem_entry () = {pki_asn1_type(), binary(), %% DER or encrypted DER not_encrypted | {"DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)}}.</code></p> <p><code>rsa_public_key() = #'RSAPublicKey'{}</code></p> @@ -72,8 +72,6 @@ <p><code>dsa_public_key() = {integer(), #'Dss-Parms'{}} </code></p> - <p><code>rsa_private_key() = #'RSAPrivateKey'{} </code></p> - <p><code>dsa_private_key() = #'DSAPrivateKey'{}</code></p> <p><code> public_crypt_options() = [{rsa_pad, rsa_padding()}]. </code></p> @@ -149,7 +147,7 @@ <name>der_decode(Asn1type, Der) -> term()</name> <fsummary> Decodes a public key asn1 der encoded entity.</fsummary> <type> - <v>Asn1Type = atom() -</v> + <v>Asn1Type = atom()</v> <d> ASN.1 type present in the public_key applications asn1 specifications.</d> <v>Der = der_encoded()</v> @@ -166,7 +164,8 @@ <v>Asn1Type = atom()</v> <d> Asn1 type present in the public_key applications ASN.1 specifications.</d> - <v>Entity = term() - The erlang representation of <c> Asn1Type</c></v> + <v>Entity = term()</v> + <d>The erlang representation of <c>Asn1Type</c></d> </type> <desc> <p> Encodes a public key entity with ASN.1 DER encoding.</p> @@ -218,12 +217,13 @@ <fsummary> Creates a pem entry that can be fed to pem_encode/1.</fsummary> <type> <v>Asn1Type = pki_asn1_type()</v> - <v>Entity = term() - The Erlang representation of + <v>Entity = term()</v> + <d>The Erlang representation of <c>Asn1Type</c>. If <c>Asn1Type</c> is 'SubjectPublicKeyInfo' then <c>Entity</c> must be either an rsa_public_key() or a dsa_public_key() and this function will create the appropriate 'SubjectPublicKeyInfo' entry. - </v> + </d> <v>CipherInfo = {"DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)}</v> <v>Password = string()</v> </type> @@ -281,7 +281,7 @@ <desc> <p>Der encodes a pkix x509 certificate or part of such a certificate. This function must be used for encoding certificates or parts of certificates - that are decoded/created on the otp format, whereas for the plain format this + that are decoded/created in the otp format, whereas for the plain format this function will directly call der_encode/2. </p> </desc> </func> diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 2901020e83..33fcce2c44 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -488,9 +488,10 @@ pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)-> _:_ -> {error, Reason} end; -pkix_path_validation(TrustedCert, CertChain, Options) when - is_binary(TrustedCert) -> OtpCert = pkix_decode_cert(TrustedCert, - otp), pkix_path_validation(OtpCert, CertChain, Options); +pkix_path_validation(TrustedCert, CertChain, Options) + when is_binary(TrustedCert) -> + OtpCert = pkix_decode_cert(TrustedCert, otp), + pkix_path_validation(OtpCert, CertChain, Options); pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) when is_list(CertChain), is_list(Options) -> diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl index 76c064f1e7..8b0f64eb45 100644 --- a/lib/reltool/src/reltool_sys_win.erl +++ b/lib/reltool/src/reltool_sys_win.erl @@ -54,7 +54,9 @@ whitelist, blacklist, derived, - fgraph_wins + fgraph_wins, + app_box, + mod_box }). -define(WIN_WIDTH, 800). @@ -86,6 +88,11 @@ -define(blacklist, "Excluded"). -define(derived, "Derived"). +-define(safe_config,{sys,[{incl_cond,exclude}, + {app,kernel,[{incl_cond,include}]}, + {app,stdlib,[{incl_cond,include}]}, + {app,sasl,[{incl_cond,include}]}]}). + -record(root_data, {dir}). -record(lib_data, {dir, tree, item}). -record(escript_data, {file, tree, item}). @@ -102,7 +109,7 @@ start_link(Opts) -> proc_lib:start_link(?MODULE, init, - [[{parent, self()} | Opts]], + [[{safe_config, false}, {parent, self()} | Opts]], infinity, []). @@ -126,53 +133,73 @@ init(Options) -> exit({Reason, erlang:get_stacktrace()}) end. -do_init([{parent, Parent} | Options]) -> +do_init([{safe_config, Safe}, {parent, Parent} | Options]) -> case reltool_server:start_link(Options) of {ok, ServerPid, C, Sys} -> process_flag(trap_exit, C#common.trap_exit), - S = #state{parent_pid = Parent, - server_pid = ServerPid, - common = C, - config_file = filename:absname("config.reltool"), - target_dir = filename:absname("reltool_target_dir"), - app_wins = [], - sys = Sys, - fgraph_wins = []}, wx:new(), wx:debug(C#common.wx_debug), - S2 = create_window(S), %% wx_misc:beginBusyCursor(), case reltool_server:get_status(ServerPid) of {ok, Warnings} -> exit_dialog(Warnings), - {ok, Sys2} = reltool_server:get_sys(ServerPid), - S3 = S2#state{sys = Sys2}, + {ok, Sys} = reltool_server:get_sys(ServerPid), + S = #state{parent_pid = Parent, + server_pid = ServerPid, + common = C, + config_file = filename:absname("config.reltool"), + target_dir = filename:absname("reltool_target_dir"), + app_wins = [], + sys = Sys, + fgraph_wins = []}, + S2 = create_window(S), S5 = wx:batch(fun() -> Title = atom_to_list(?APPLICATION), - wxFrame:setTitle(S3#state.frame, + wxFrame:setTitle(S2#state.frame, Title), %% wxFrame:setMinSize(Frame, %% {?WIN_WIDTH, ?WIN_HEIGHT}), wxStatusBar:setStatusText( - S3#state.status_bar, + S2#state.status_bar, "Done."), - S4 = redraw_apps(S3), - redraw_libs(S4) + S3 = redraw_apps(S2), + S4 = redraw_libs(S3), + redraw_config_page(S4) end), %% wx_misc:endBusyCursor(), %% wxFrame:destroy(Frame), proc_lib:init_ack(S#state.parent_pid, {ok, self()}), loop(S5); {error, Reason} -> - io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), - exit(Reason) + restart_server_safe_config(Safe,Parent,Reason) end; {error, Reason} -> io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), exit(Reason) end. +restart_server_safe_config(true,_Parent,Reason) -> + io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), + exit(Reason); +restart_server_safe_config(false,Parent,Reason) -> + Strings = + [{?wxBLACK,"Could not start reltool server:\n\n"}, + {?wxRED,Reason++"\n\n"}, + {?wxBLACK, + io_lib:format( + "Resetting the configuration to:~n~n ~p~n~n" + "Do you want to continue with this configuration?", + [?safe_config])}], + + case question_dialog_2("Reltool server start error", Strings) of + ?wxID_OK -> + do_init([{safe_config,true},{parent,Parent},?safe_config]); + ?wxID_CANCEL -> + io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), + exit(Reason) + end. + exit_dialog([]) -> ok; exit_dialog(Warnings) -> @@ -606,6 +633,13 @@ create_config_page(#state{sys = Sys, book = Book} = S) -> {proportion, 1}]), wxPanel:setSizer(Panel, Sizer), wxNotebook:addPage(Book, Panel, ?SYS_PAGE, []), + S#state{app_box = AppBox, mod_box = ModBox}. + +redraw_config_page(#state{sys = Sys, app_box = AppBox, mod_box = ModBox} = S) -> + AppChoice = reltool_utils:incl_cond_to_index(Sys#sys.incl_cond), + wxRadioBox:setSelection(AppBox, AppChoice), + ModChoice = reltool_utils:mod_cond_to_index(Sys#sys.mod_cond), + wxRadioBox:setSelection(ModBox, ModChoice), S. create_main_release_page(#state{book = Book} = S) -> @@ -640,15 +674,15 @@ create_main_release_page(#state{book = Book} = S) -> add_release_page(Book, #rel{name = RelName, rel_apps = RelApps}) -> Panel = wxPanel:new(Book, []), Sizer = wxBoxSizer:new(?wxHORIZONTAL), - RelBox = wxRadioBox:new(Panel, - ?wxID_ANY, - "Applications included in the release " ++ RelName, - ?wxDefaultPosition, - ?wxDefaultSize, - [atom_to_list(RA#rel_app.name) || RA <- RelApps], - []), - %% wxRadioBox:setSelection(RelBox, 2), % mandatory - wxEvtHandler:connect(RelBox, command_radiobox_selected, + AppNames = [kernel, stdlib | + [RA#rel_app.name || RA <- RelApps] -- [kernel, stdlib]], + RelBox = wxListBox:new( + Panel,?wxID_ANY, + [{pos,?wxDefaultPosition}, + {size,?wxDefaultSize}, + {choices,[[atom_to_list(AppName)] || AppName <- AppNames]}, + {style,?wxLB_EXTENDED}]), + wxEvtHandler:connect(RelBox, command_listbox_selected, [{userData, {config_rel_cond, RelName}}]), RelToolTip = "Choose which applications that shall " "be included in the release resource file.", @@ -1363,7 +1397,8 @@ refresh(S) -> [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- S#state.app_wins], S2 = S#state{sys = Sys}, S3 = redraw_libs(S2), - redraw_apps(S3). + S4 = redraw_apps(S3), + redraw_config_page(S4). question_dialog(Question, Details) -> %% Parent = S#state.frame, @@ -1420,6 +1455,44 @@ display_message(Message, Icon) -> wxMessageDialog:showModal(Dialog), wxMessageDialog:destroy(Dialog). +%% Strings = [{Color,String}] +question_dialog_2(DialogLabel, Strings) -> + %% Parent = S#state.frame, + Parent = wx:typeCast(wx:null(), wxWindow), + %% [{style, ?wxYES_NO bor ?wxICON_ERROR bor ?wx}]), + DialogStyle = ?wxRESIZE_BORDER bor ?wxCAPTION bor ?wxSYSTEM_MENU bor + ?wxMINIMIZE_BOX bor ?wxMAXIMIZE_BOX bor ?wxCLOSE_BOX, + Dialog = wxDialog:new(Parent, ?wxID_ANY, DialogLabel, + [{style, DialogStyle}]), + Color = wxWindow:getBackgroundColour(Dialog), + TextStyle = ?wxTE_READONLY bor ?wxTE_MULTILINE bor ?wxHSCROLL, + Text = wxTextCtrl:new(Dialog, ?wxID_ANY, + [{size, {600, 400}}, {style, TextStyle}]), + wxWindow:setBackgroundColour(Text, Color), + TextAttr = wxTextAttr:new(), + add_text(Text,TextAttr,Strings), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Text, [{border, 2}, {flag, ?wxEXPAND}, {proportion, 1}]), + ButtSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(Sizer, ButtSizer, [{border, 2}, {flag, ?wxEXPAND}]), + wxPanel:setSizer(Dialog, Sizer), + wxSizer:fit(Sizer, Dialog), + wxSizer:setSizeHints(Sizer, Dialog), + Answer = wxDialog:showModal(Dialog), + wxDialog:destroy(Dialog), + Answer. + +add_text(Text,Attr,[{Color,String}|Strings]) -> + wxTextAttr:setTextColour(Attr, Color), + wxTextCtrl:setDefaultStyle(Text, Attr), + wxTextCtrl:appendText(Text, String), + add_text(Text,Attr,Strings); +add_text(_,_,[]) -> + ok. + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% sys callbacks diff --git a/lib/sasl/src/erlsrv.erl b/lib/sasl/src/erlsrv.erl index f9804c41dc..086dc7c651 100644 --- a/lib/sasl/src/erlsrv.erl +++ b/lib/sasl/src/erlsrv.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% Copyright Ericsson AB 1998-2011. 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 @@ -75,14 +75,21 @@ write_all_data(Port,[H|T]) -> write_all_data(Port,T). read_all_data(Port) -> + lists:reverse(read_all_data(Port,[],[])). +read_all_data(Port,Line,Lines) -> receive + {Port, {data, {noeol,Data}}} -> + read_all_data(Port,Line++Data,Lines); {Port, {data, {eol,Data}}} -> - [ Data | read_all_data(Port)]; - _ -> + read_all_data(Port,[],[Line++Data|Lines]); + {Port,_Other} -> Port ! {self(), close}, receive {Port, closed} -> - [] + case Line of + [] -> Lines; + _ -> [Line|Lines] + end end end. @@ -208,7 +215,7 @@ store_service(EmulatorVersion,Service) -> false -> {error, no_servicename}; {value, {_,Name}} -> - {Action,Service1} = case get_service(Name) of + {Action,Service1} = case get_service(EmulatorVersion,Name) of {error, no_such_service} -> {"add",Service}; _ -> @@ -377,8 +384,14 @@ pick_argument(_,[],Acc) -> {Acc, ""}; pick_argument(normal,[$ |T],Acc) -> {Acc,T}; +pick_argument(normal,[$\\|T],Acc) -> + pick_argument(normal_escaped,T,[$\\|Acc]); pick_argument(normal,[$"|T],Acc) -> pick_argument(quoted,T,[$"|Acc]); +pick_argument(normal_escaped,[$"|T],Acc) -> + pick_argument(bquoted,T,[$"|Acc]); +pick_argument(normal_escaped,[A|T],Acc) -> + pick_argument(normal,T,[A|Acc]); pick_argument(quoted_escaped,[H|T],Acc) -> pick_argument(quoted,T,[H|Acc]); pick_argument(quoted,[$"|T],Acc) -> @@ -387,6 +400,14 @@ pick_argument(quoted,[$\\|T],Acc) -> pick_argument(quoted_escaped,T,[$\\|Acc]); pick_argument(quoted,[H|T],Acc) -> pick_argument(quoted,T,[H|Acc]); +pick_argument(bquoted_escaped,[$"|T],Acc) -> + pick_argument(normal,T,[$"|Acc]); +pick_argument(bquoted_escaped,[H|T],Acc) -> + pick_argument(bquoted,T,[H|Acc]); +pick_argument(bquoted,[$\\|T],Acc) -> + pick_argument(bquoted_escaped,T,[$\\|Acc]); +pick_argument(bquoted,[H|T],Acc) -> + pick_argument(bquoted,T,[H|Acc]); pick_argument(normal,[H|T],Acc) -> pick_argument(normal,T,[H|Acc]). diff --git a/lib/sasl/src/release_handler.erl b/lib/sasl/src/release_handler.erl index b60aa847df..ab54b1d00b 100644 --- a/lib/sasl/src/release_handler.erl +++ b/lib/sasl/src/release_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% Copyright Ericsson AB 1996-2011. 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,8 @@ check_script(Script, LibDirs) -> release_handler_1:check_script(Script, LibDirs). %%----------------------------------------------------------------- -%% eval_script(Script, Apps, LibDirs, Opts) -> {ok, UnPurged} | +%% eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> +%% {ok, UnPurged} | %% restart_new_emulator | %% {error, Error} %% {'EXIT', Reason} @@ -299,9 +300,13 @@ check_script(Script, LibDirs) -> %% net_kernel:monitor_nodes(true) before calling this function. %% No! No other process than the release_handler can ever call this %% function, if sync_nodes is used. -%%----------------------------------------------------------------- -eval_script(Script, Apps, LibDirs, Opts) -> - catch release_handler_1:eval_script(Script, Apps, LibDirs, Opts). +%% +%% LibDirs is a list of all applications, while NewLibs is a list of +%% applications that have changed version between the current and the +%% new release. +%% ----------------------------------------------------------------- +eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> + catch release_handler_1:eval_script(Script, Apps, LibDirs, NewLibs, Opts). %%----------------------------------------------------------------- %% Func: create_RELEASES(Root, RelFile, LibDirs) -> ok | {error, Reason} @@ -405,6 +410,7 @@ eval_appup_script(App, ToVsn, ToDir, Script) -> Res = release_handler_1:eval_script(Script, [], % [AppSpec] [{App, ToVsn, ToDir}], + [{App, ToVsn, ToDir}], []), % [Opt] case Res of {ok, _Unpurged} -> @@ -890,6 +896,7 @@ do_check_install_release(RelDir, Vsn, Releases, Masters) -> end. do_install_release(#state{start_prg = StartPrg, + root = RootDir, rel_dir = RelDir, releases = Releases, masters = Masters, static_emulator = Static}, @@ -905,7 +912,9 @@ do_install_release(#state{start_prg = StartPrg, EnvBefore = application_controller:prep_config_change(), Apps = change_appl_data(RelDir, Release, Masters), LibDirs = Release#release.libs, - case eval_script(Script, Apps, LibDirs, Opts) of + NewLibs = get_new_libs(LatestRelease#release.libs, + Release#release.libs), + case eval_script(Script, Apps, LibDirs, NewLibs, Opts) of {ok, []} -> application_controller:config_change(EnvBefore), mon_nodes(false), @@ -926,8 +935,8 @@ do_install_release(#state{start_prg = StartPrg, NReleases = set_status(Vsn, current, Releases), NReleases2 = set_status(Vsn,tmp_current,NReleases), write_releases(RelDir, NReleases2, Masters), - prepare_restart_new_emulator(StartPrg, RelDir, - Release, + prepare_restart_new_emulator(StartPrg, RootDir, + RelDir, Release, PermanentRelease, Masters), {restart_new_emulator, CurrentVsn, Descr}; @@ -997,7 +1006,7 @@ do_make_services_permanent(PermanentVsn,Vsn, PermanentEVsn, EVsn) -> throw(Error4) end end. - + do_make_permanent(#state{releases = Releases, rel_dir = RelDir, unpurged = Unpurged, masters = Masters, @@ -1409,8 +1418,8 @@ prepare_restart_nt(#release{erts_vsn = EVsn, vsn = Vsn}, FutureServiceName = hd(string:tokens(atom_to_list(node()),"@")) ++ "_" ++ Vsn, CurrentService = case erlsrv:get_service(PermEVsn,CurrentServiceName) of - {error, Reason} -> - throw({error, Reason}); + {error, _} = Error1 -> + throw(Error1); CS -> CS end, @@ -1425,37 +1434,33 @@ prepare_restart_nt(#release{erts_vsn = EVsn, vsn = Vsn}, CurrentServiceName), case erlsrv:store_service(EVsn, FutureService) of - {error, Rison} -> - throw({error,Rison}); - _ -> + {error, _} = Error2 -> + throw(Error2); + _X -> erlsrv:disable_service(EVsn, FutureServiceName), ErlSrv = filename:nativename(erlsrv:erlsrv(EVsn)), - case heart:set_cmd(ErlSrv ++ " enable " ++ FutureServiceName ++ - " & " ++ ErlSrv ++ " start " ++ - FutureServiceName ++ - " & " ++ ErlSrv ++ " disable " ++ - FutureServiceName) of + StartDisabled = ErlSrv ++ " start_disabled " ++ FutureServiceName, + case heart:set_cmd(StartDisabled) of ok -> ok; - Error -> - throw({error, {'heart:set_cmd() error', Error}}) + Error3 -> + throw({error, {'heart:set_cmd() error', Error3}}) end end. - %%----------------------------------------------------------------- %% Set things up for restarting the new emulator. The actual %% restart is performed by calling init:reboot() higher up. %%----------------------------------------------------------------- -prepare_restart_new_emulator(StartPrg, RelDir, - Release, PRelease, - Masters) -> +prepare_restart_new_emulator(StartPrg, RootDir, RelDir, + Release, PRelease, Masters) -> #release{erts_vsn = EVsn, vsn = Vsn} = Release, Data = EVsn ++ " " ++ Vsn, DataFile = write_new_start_erl(Data, RelDir, Masters), %% Tell heart to use DataFile instead of start_erl.data case os:type() of {win32,nt} -> + write_ini_file(RootDir,EVsn,Masters), prepare_restart_nt(Release,PRelease,DataFile); {unix,_} -> StartP = check_start_prg(StartPrg, Masters), @@ -1832,50 +1837,10 @@ write_start(File, Data, false) -> end; write_start(File, Data, Masters) -> all_masters(Masters), - write_start_m(File, Data, Masters). + safe_write_file_m(File, Data, Masters). %%----------------------------------------------------------------- -%% Write the "start_erl.data" file at all master nodes. -%% 1. Save "start_erl.backup" at all nodes. -%% 2. Write the "start_erl.change" file at all nodes. -%% 3. Move "start_erl.change" to "start_erl.data". -%% 4. Remove "start_erl.backup" at all nodes. -%% -%% If one of the steps above fails, all steps is recovered from -%% (as long as possible), except for 4 which is allowed to fail. -%%----------------------------------------------------------------- -write_start_m(File, Data, Masters) -> - Dir = filename:dirname(File), - Backup = filename:join(Dir, "start_erl.backup"), - Change = filename:join(Dir, "start_erl.change"), - case at_all_masters(Masters, ?MODULE, do_copy_files, - [File, [Backup]]) of - ok -> - case at_all_masters(Masters, ?MODULE, do_write_file, - [Change, Data]) of - ok -> - case at_all_masters(Masters, file, rename, - [Change, File]) of - ok -> - remove_files(all, [Backup, Change], Masters), - ok; - {error, {Master, R}} -> - takewhile(Master, Masters, file, rename, - [Backup, File]), - remove_files(all, [Backup, Change], Masters), - throw({error, {Master, R, move_start_erl}}) - end; - {error, {Master, R}} -> - remove_files(all, [Backup, Change], Masters), - throw({error, {Master, R, write_start_erl}}) - end; - {error, {Master, R}} -> - remove_files(Master, [Backup], Masters), - throw({error, {Master, R, backup_start_erl}}) - end. - -%%----------------------------------------------------------------- %% Copy the "start.boot" and "sys.config" from SrcDir to DestDir at all %% master nodes. %% 1. Save DestDir/"start.backup" and DestDir/"sys.backup" at all nodes. @@ -1917,3 +1882,97 @@ set_static_files(SrcDir, DestDir, Masters) -> remove_files(Master, [BackupBoot, BackupConf], Masters), throw({error, {Master, R, backup_start_config}}) end. + +%%----------------------------------------------------------------- +%% Write erl.ini +%% Writes the erl.ini file used by erl.exe when (re)starting the erlang node. +%% At first installation, this is done by Install.exe, which means that if +%% the format of this file for some reason is changed, then Install.c must +%% also be updated (and probably some other c-files which read erl.ini) +%%----------------------------------------------------------------- +write_ini_file(RootDir,EVsn,Masters) -> + BinDir = filename:join([RootDir,"erts-"++EVsn,"bin"]), + Str0 = io_lib:format("[erlang]~n" + "Bindir=~s~n" + "Progname=erl~n" + "Rootdir=~s~n", + [filename:nativename(BinDir), + filename:nativename(RootDir)]), + Str = re:replace(Str0,"\\\\","\\\\\\\\",[{return,list},global]), + IniFile = filename:join(BinDir,"erl.ini"), + do_write_ini_file(IniFile,Str,Masters). + +do_write_ini_file(File,Data,false) -> + case do_write_file(File, Data) of + ok -> ok; + Error -> throw(Error) + end; +do_write_ini_file(File,Data,Masters) -> + all_masters(Masters), + safe_write_file_m(File, Data, Masters). + + +%%----------------------------------------------------------------- +%% Write the given file at all master nodes. +%% 1. Save <File>.backup at all nodes. +%% 2. Write <File>.change at all nodes. +%% 3. Move <File>.change to <File> +%% 4. Remove <File>.backup at all nodes. +%% +%% If one of the steps above fails, all steps are recovered from +%% (as long as possible), except for 4 which is allowed to fail. +%%----------------------------------------------------------------- +safe_write_file_m(File, Data, Masters) -> + Backup = File ++ ".backup", + Change = File ++ ".change", + case at_all_masters(Masters, ?MODULE, do_copy_files, + [File, [Backup]]) of + ok -> + case at_all_masters(Masters, ?MODULE, do_write_file, + [Change, Data]) of + ok -> + case at_all_masters(Masters, file, rename, + [Change, File]) of + ok -> + remove_files(all, [Backup, Change], Masters), + ok; + {error, {Master, R}} -> + takewhile(Master, Masters, file, rename, + [Backup, File]), + remove_files(all, [Backup, Change], Masters), + throw({error, {Master, R, rename, + filename:basename(Change), + filename:basename(File)}}) + end; + {error, {Master, R}} -> + remove_files(all, [Backup, Change], Masters), + throw({error, {Master, R, write, filename:basename(Change)}}) + end; + {error, {Master, R}} -> + remove_files(Master, [Backup], Masters), + throw({error, {Master, R, backup, + filename:basename(File), + filename:basename(Backup)}}) + end. + +%%----------------------------------------------------------------- +%% Figure out which applications that have changed version between the +%% two releases. The paths for these applications must always be +%% updated, even if the relup script does not load any modules. See +%% OTP-9402. +%% +%% A different situation is when the same application version is used +%% in old and new release, but the path has changed. This is not +%% handled here - instead it must be explicitely indicated by the +%% 'update_paths' option to release_handler:install_release/2 if the +%% code path shall be updated then. +%% ----------------------------------------------------------------- +get_new_libs([{App,Vsn,LibDir}|CurrentLibs], NewLibs) -> + case lists:keyfind(App,1,NewLibs) of + {App,NewVsn,_} = LibInfo when NewVsn =/= Vsn -> + [LibInfo | get_new_libs(CurrentLibs,NewLibs)]; + _ -> + get_new_libs(CurrentLibs,NewLibs) + end; +get_new_libs([],_) -> + []. diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl index 8d050fb7b0..ff62f847ac 100644 --- a/lib/sasl/src/release_handler_1.erl +++ b/lib/sasl/src/release_handler_1.erl @@ -19,7 +19,7 @@ -module(release_handler_1). %% External exports --export([eval_script/3, eval_script/4, check_script/2]). +-export([eval_script/1, eval_script/5, check_script/2]). -export([get_current_vsn/1]). %% exported because used in a test case -record(eval_state, {bins = [], stopped = [], suspended = [], apps = [], @@ -33,11 +33,11 @@ %% libdirs = [{Lib, LibVsn, LibDir}] - Maps Lib to Vsn and Directory %% unpurged = [{Mod, soft_purge | brutal_purge}] %% vsns = [{Mod, OldVsn, NewVsn}] - remember the old vsn of a mod -%% before it is removed/a new vsn is loaded; the new vsn +%% before a new vsn is loaded; the new vsn %% is kept in case of a downgrade, where the code_change %% function receives the vsn of the module to downgrade %% *to*. -%% newlibs = [{Lib, Dir}] - list of all new libs; used to change +%% newlibs = [{Lib, LibVsn, LibDir}] - list of all new libs; used to change %% the code path %% opts = [{Tag, Value}] - list of options %%----------------------------------------------------------------- @@ -63,10 +63,11 @@ check_script(Script, LibDirs) -> {error, {old_processes, Mod}} end. -eval_script(Script, Apps, LibDirs) -> - eval_script(Script, Apps, LibDirs, []). +%% eval_script/1 - For testing only - no apps added, just testing instructions +eval_script(Script) -> + eval_script(Script, [], [], [], []). -eval_script(Script, Apps, LibDirs, Opts) -> +eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> case catch check_old_processes(Script) of ok -> {Before, After} = split_instructions(Script), @@ -75,6 +76,7 @@ eval_script(Script, Apps, LibDirs, Opts) -> end, #eval_state{apps = Apps, libdirs = LibDirs, + newlibs = NewLibs, opts = Opts}, Before) of EvalState2 when is_record(EvalState2, eval_state) -> @@ -214,16 +216,15 @@ check_old_code(Mod) -> %%----------------------------------------------------------------- eval({load_object_code, {Lib, LibVsn, Modules}}, EvalState) -> case lists:keysearch(Lib, 1, EvalState#eval_state.libdirs) of - {value, {Lib, LibVsn, LibDir}} -> - Ebin = filename:join(LibDir, "ebin"), + {value, {Lib, LibVsn, LibDir} = LibInfo} -> Ext = code:objfile_extension(), {NewBins, NewVsns} = lists:foldl(fun(Mod, {Bins, Vsns}) -> File = lists:concat([Mod, Ext]), - FName = filename:join(Ebin, File), + FName = filename:join([LibDir, "ebin", File]), case erl_prim_loader:get_file(FName) of {ok, Bin, FName2} -> - NVsns = add_new_vsn(Mod, Bin, Vsns), + NVsns = add_vsns(Mod, Bin, Vsns), {[{Mod, Bin, FName2} | Bins],NVsns}; error -> throw({error, {no_such_file,FName}}) @@ -232,7 +233,7 @@ eval({load_object_code, {Lib, LibVsn, Modules}}, EvalState) -> {EvalState#eval_state.bins, EvalState#eval_state.vsns}, Modules), - NewLibs = [{Lib, Ebin} | EvalState#eval_state.newlibs], + NewLibs = lists:keystore(Lib,1,EvalState#eval_state.newlibs,LibInfo), EvalState#eval_state{bins = NewBins, newlibs = NewLibs, vsns = NewVsns}; @@ -242,15 +243,14 @@ eval({load_object_code, {Lib, LibVsn, Modules}}, EvalState) -> eval(point_of_no_return, EvalState) -> Libs = case get_opt(update_paths, EvalState, false) of false -> - EvalState#eval_state.newlibs; % [{Lib, Path}] + EvalState#eval_state.newlibs; true -> - lists:map(fun({Lib, _LibVsn, LibDir}) -> - Ebin= filename:join(LibDir,"ebin"), - {Lib, Ebin} - end, - EvalState#eval_state.libdirs) + EvalState#eval_state.libdirs end, - lists:foreach(fun({Lib, Path}) -> code:replace_path(Lib, Path) end, + lists:foreach(fun({Lib, _LibVsn, LibDir}) -> + Ebin = filename:join(LibDir,"ebin"), + code:replace_path(Lib, Ebin) + end, Libs), EvalState; eval({load, {Mod, _PrePurgeMethod, PostPurgeMethod}}, EvalState) -> @@ -258,32 +258,21 @@ eval({load, {Mod, _PrePurgeMethod, PostPurgeMethod}}, EvalState) -> {value, {_Mod, Bin, File}} = lists:keysearch(Mod, 1, Bins), % load_binary kills all procs running old code % if soft_purge, we know that there are no such procs now - Vsns = EvalState#eval_state.vsns, - NewVsns = add_old_vsn(Mod, Vsns), code:load_binary(Mod, File, Bin), % Now, the prev current is old. There might be procs % running it. Find them. Unpurged = do_soft_purge(Mod,PostPurgeMethod,EvalState#eval_state.unpurged), EvalState#eval_state{bins = lists:keydelete(Mod, 1, Bins), - unpurged = Unpurged, - vsns = NewVsns}; + unpurged = Unpurged}; eval({remove, {Mod, _PrePurgeMethod, PostPurgeMethod}}, EvalState) -> - % purge kills all procs running old code - % if soft_purge, we know that there are no such procs now - Vsns = EvalState#eval_state.vsns, - NewVsns = add_old_vsn(Mod, Vsns), + %% purge kills all procs running old code + %% if soft_purge, we know that there are no such procs now code:purge(Mod), code:delete(Mod), - % Now, the prev current is old. There might be procs - % running it. Find them. - Unpurged = - case code:soft_purge(Mod) of - true -> EvalState#eval_state.unpurged; - false -> [{Mod, PostPurgeMethod} | EvalState#eval_state.unpurged] - end, -%% Bins = EvalState#eval_state.bins, -%% EvalState#eval_state{bins = lists:keydelete(Mod, 1, Bins), - EvalState#eval_state{unpurged = Unpurged, vsns = NewVsns}; + %% Now, the prev current is old. There might be procs + %% running it. Find them. + Unpurged = do_soft_purge(Mod,PostPurgeMethod,EvalState#eval_state.unpurged), + EvalState#eval_state{unpurged = Unpurged}; eval({purge, Modules}, EvalState) -> % Now, if there are any processes still executing old code, OR % if some new processes started after suspend but before load, @@ -606,26 +595,20 @@ sync_nodes(Id, Nodes) -> end, NNodes). -add_old_vsn(Mod, Vsns) -> +add_vsns(Mod, NewBin, Vsns) -> + OldVsn = get_current_vsn(Mod), + NewVsn = get_vsn(NewBin), case lists:keysearch(Mod, 1, Vsns) of - {value, {Mod, undefined, NewVsn}} -> - OldVsn = get_current_vsn(Mod), - lists:keyreplace(Mod, 1, Vsns, {Mod, OldVsn, NewVsn}); - {value, {Mod, _OldVsn, _NewVsn}} -> - Vsns; + {value, {Mod, OldVsn0, NewVsn0}} -> + lists:keyreplace(Mod, 1, Vsns, {Mod, + replace_undefined(OldVsn0,OldVsn), + replace_undefined(NewVsn0,NewVsn)}); false -> - OldVsn = get_current_vsn(Mod), - [{Mod, OldVsn, undefined} | Vsns] + [{Mod, OldVsn, NewVsn} | Vsns] end. -add_new_vsn(Mod, Bin, Vsns) -> - NewVsn = get_vsn(Bin), - case lists:keysearch(Mod, 1, Vsns) of - {value, {Mod, OldVsn, undefined}} -> - lists:keyreplace(Mod, 1, Vsns, {Mod, OldVsn, NewVsn}); - false -> - [{Mod, undefined, NewVsn} | Vsns] - end. +replace_undefined(undefined,Vsn) -> Vsn; +replace_undefined(Vsn,_) -> Vsn. %%----------------------------------------------------------------- %% Func: get_current_vsn/1 @@ -645,7 +628,9 @@ get_current_vsn(Mod) -> {ok, Bin, _File2} -> get_vsn(Bin); error -> - throw({error, {no_such_file, File}}) + %% This is the case when a new module is added, there will + %% be no current version of it at the time of this call. + undefined end. %%----------------------------------------------------------------- diff --git a/lib/sasl/src/systools_lib.erl b/lib/sasl/src/systools_lib.erl index b652c109fe..f951647b79 100644 --- a/lib/sasl/src/systools_lib.erl +++ b/lib/sasl/src/systools_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% Copyright Ericsson AB 1996-2011. 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 @@ -176,21 +176,26 @@ add_dirs(RegName, Dirs, Root) -> regexp_match(RegName, D0, Root) -> case file:list_dir(D0) of {ok, Files} when length(Files) > 0 -> - FR = fun(F) -> - case regexp:match(F, RegName) of - {match,1,N} when N == length(F) -> - DirF = join(D0, F, Root), - case dir_p(DirF) of - true -> - {true, DirF}; + case re:compile(RegName) of + {ok, MP} -> + FR = fun(F) -> + case re:run(F, MP) of + {match,[{0,N}]} when N == length(F) -> + DirF = join(D0, F, Root), + case dir_p(DirF) of + true -> + {true, DirF}; + _ -> + false + end; _ -> false - end; - _ -> - false - end - end, - {true,lists:zf(FR, Files)}; + end + end, + {true,lists:zf(FR, Files)}; + _ -> + false + end; _ -> false end. diff --git a/lib/sasl/test/Makefile b/lib/sasl/test/Makefile index 2d92305fcf..65be134462 100644 --- a/lib/sasl/test/Makefile +++ b/lib/sasl/test/Makefile @@ -31,7 +31,8 @@ MODULES= \ systools_SUITE \ systools_rc_SUITE \ overload_SUITE \ - rb_SUITE + rb_SUITE \ + rh_test_lib ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/sasl/test/installer.erl b/lib/sasl/test/installer.erl index a114c4b5c9..f5ceab0dc4 100644 --- a/lib/sasl/test/installer.erl +++ b/lib/sasl/test/installer.erl @@ -119,6 +119,7 @@ install_3(TestNode,PrivDir) -> ?print(["install_3 unpack_release P2A ok"]), ?check_release("P2A",unpacked,["a-1.1"]), {ok, "P1I", [new_emu]} = release_handler:check_install_release("P2A"), + ?print(["install_3 check_install_release P2A ok"]), ok = release_handler:make_permanent("P1I"), ?print(["install_3 make_permanent P1I ok"]), ?check_release("P1I",permanent,["a-1.1"]), @@ -268,23 +269,30 @@ client1_1(TestNode,PrivDir,MasterDir,ClientSname) -> erl_boot_server:start([IP]), ok = net_kernel:monitor_nodes(true), - Node = start_client(TestNode,ClientSname), + Node = start_client(TestNode,client1,ClientSname), trace_disallowed_calls(Node), %% Check env var for SASL on client node SaslEnv = rpc:call(Node, application, get_all_env, [sasl]), + ?print([{client1_1,sasl_env},SaslEnv]), {_,CliDir} = lists:keyfind(client_directory,1,SaslEnv), {_,[Master]} = lists:keyfind(masters,1,SaslEnv), {_,StartCli} = lists:keyfind(start_prg,1,SaslEnv), - Root = code:root_dir(), - true = (CliDir =:= filename:join([Root,"clients","type1",Node])), - true = (StartCli =:= filename:join([CliDir,"bin","start"])), + NodeStr = atom_to_list(Node), + [NodeStr,"type1","clients"|_] = lists:reverse(filename:split(CliDir)), true = (Master =:= node()), + case os:type() of + {unix,_} -> + true = (StartCli =:= filename:join([CliDir,"bin","start"])); + _ -> + ok + end, %% Unpack P1H on master {ok, "P1H"} = unpack_release(PrivDir,"rel1"), %% Unpack and install P1H on client + Root = code:root_dir(), P1HDir = filename:join([Root, "releases", "P1H"]), %% The AppDirs argument (last arg to set_unpacked) below is really @@ -339,6 +347,7 @@ client1_2(TestNode,PrivDir,Node) -> ?check_running_app_client(Node,a,"1.0"), ok = rpc:call(Node, release_handler, make_permanent, ["P1H"]), + ?check_release_client(Node,"P1H",permanent,["a-1.0"]), check_disallowed_calls(), reboot(TestNode,Node), @@ -584,7 +593,7 @@ trace_disallowed_calls(Node) -> MasterProc = self(), rpc:call(Node,dbg,tracer,[process,{fun(T,_) -> MasterProc ! T end,[]}]), rpc:call(Node,dbg,p,[all,call]), - rpc:call(Node,dbg,tp,[file,[]]). + rpc:call(Node,dbg,tp,[file,[{'_',[],[{message,{caller}}]}]]). check_disallowed_calls() -> receive @@ -594,13 +603,12 @@ check_disallowed_calls() -> ok end. -start_client(TestNode,Client) -> - {Start, Node} = do_start_client(Client,test_host()), - Cmd = lists:concat(["env NODENAME=",Client," ", - filename:join(code:root_dir(), Start)]), - ?print([{start_client,Client},Cmd]), - Res = os:cmd(Cmd), - ?print([{start_client,result},Res]), +start_client(TestNode,Client,Sname) -> + Node = list_to_atom(lists:concat([Sname,"@",test_host()])), + case os:type() of + {unix,_} -> start_client_unix(TestNode,Sname,Node); + {win32,_} -> start_client_win32(TestNode,Client,Sname) + end, receive {nodeup, Node} -> wait_started(TestNode,Node) @@ -609,10 +617,34 @@ start_client(TestNode,Client) -> ?fail({"can not start", Node}) end. -do_start_client(Client, Host) -> - Node = list_to_atom(lists:concat([Client,"@",Host])), +start_client_unix(TestNode,Sname,Node) -> Start = filename:join(["clients", "type1", Node, "bin", "start"]), - {Start, Node}. + Cmd = lists:concat(["env NODENAME=",Sname," ", + filename:join(code:root_dir(), Start)]), + ?print([{start_client,Sname},Cmd]), + Res = os:cmd(Cmd), + ?print([{start_client,result},Res]). + +start_client_win32(TestNode,Client,ClientSname) -> + Name = atom_to_list(ClientSname) ++ "_P1G", + RootDir = code:root_dir(), + ErtsBinDir = filename:join(RootDir,"erts-4.4/bin"), + + {ClientArgs,RelClientDir} = rh_test_lib:get_client_args(Client,ClientSname, + RootDir), + StartErlArgs = rh_test_lib:get_start_erl_args(RootDir,RelClientDir, + ClientArgs), + ServiceArgs = rh_test_lib:get_service_args(RootDir, RelClientDir, + ClientSname, StartErlArgs), + + ?print([{start_client,ClientSname},ServiceArgs]), + Erlsrv = filename:nativename(filename:join(ErtsBinDir,"erlsrv")), + rh_test_lib:erlsrv(Erlsrv,stop,Name), + rh_test_lib:erlsrv(Erlsrv,remove,Name), + ok = rh_test_lib:erlsrv(Erlsrv,add,Name,ServiceArgs), + ok = rh_test_lib:erlsrv(Erlsrv,start,Name), + ?print([{start_client,result},ok]), + ok. reboot(TestNode,Node) -> cover_client(TestNode,Node,stop_cover), @@ -628,7 +660,7 @@ check_reboot(TestNode,Node) -> receive {nodeup, Node} -> wait_started(TestNode,Node) after 30000 -> - ?fail({Node, "not rebooted",net_adm:ping(Node)}) + ?fail({Node, "not rebooted",net_adm:ping(Node)}) end after 30000 -> ?fail({Node, "not closing down",net_adm:ping(Node)}) @@ -678,22 +710,28 @@ client2(TestNode,PrivDir,ClientSname) -> release_handler:remove_release("P1H"), ok = net_kernel:monitor_nodes(true), - Node = start_client(TestNode,ClientSname), + Node = start_client(TestNode,client2,ClientSname), %% Check env var for SASL on client node - ?print([{sasl_env, Node}, rpc:call(Node, application, get_all_env, [sasl])]), SaslEnv = rpc:call(Node, application, get_all_env, [sasl]), + ?print([{client1_1,sasl_env},SaslEnv]), {_,CliDir} = lists:keyfind(client_directory,1,SaslEnv), {_,[Master,Master2]} = lists:keyfind(masters,1,SaslEnv), {_,StartCli} = lists:keyfind(start_prg,1,SaslEnv), - Root = code:root_dir(), - true = (CliDir =:= filename:join([Root,"clients","type1",Node])), - true = (StartCli =:= filename:join([CliDir,"bin","start"])), + NodeStr = atom_to_list(Node), + [NodeStr,"type1","clients"|_] = lists:reverse(filename:split(CliDir)), true = (Master =:= node()), true = (Master2 =:= list_to_atom("master2@"++TestHost)), + case os:type() of + {unix,_} -> + true = (StartCli =:= filename:join([CliDir,"bin","start"])); + _ -> + ok + end, {ok, "P1H"} = unpack_release(PrivDir,"rel1"), + Root = code:root_dir(), {error,{bad_masters,[Master2]}} = rpc:call(Node, release_handler, set_unpacked, [filename:join([Root, "releases", "P1H", "rel1.rel"]),[]]), diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index efa775f344..9c7733b7ec 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -51,11 +51,11 @@ unix_cases() -> [target_system] ++ RunErlCases ++ cases(). win32_cases() -> - cases(). + [{group,release} | cases()]. %% Cases that can be run on all platforms cases() -> - [otp_2740, otp_2760, otp_5761, instructions, eval_appup]. + [otp_2740, otp_2760, otp_5761, otp_9402, otp_9417, instructions, eval_appup]. groups() -> [{release,[], @@ -148,6 +148,10 @@ init_per_group(release_gg, Config0) -> end_per_group(release, Config) -> Dog = ?t:timetrap(?default_timeout), stop_print_proc(), + case os:type() of + {win32,_} -> delete_all_services(); + _ -> ok + end, delete_release(Config), ?t:timetrap_cancel(Dog), Config; @@ -169,6 +173,10 @@ end_per_testcase(Case, Config) -> Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + %% DEBUG case ?config(tc_status,Config) of ok -> @@ -206,10 +214,6 @@ end_per_testcase(Case, Config) -> %% immediately restarted by heart and the test cases wait until %% the node is actually up and running -- see wait_nodes_up/2) file:delete("sasl_erl_crash.dump"), - - try apply(?MODULE,Case,[cleanup,Config]) - catch error:undef -> ok - end, ok. gg_node_snames(Config) -> @@ -224,7 +228,10 @@ gg_node_snames(Config) -> no_run_erl(Config) when is_list(Config) -> {comment, "No run_erl program"}. - +break(Config) -> + erlang:display(test_break), + ?t:break(priv_dir(Config)), + ok. %% Test upgrade and downgrade of erts upgrade(Conf) when is_list(Conf) -> @@ -323,7 +330,7 @@ client1(Conf) when is_list(Conf) -> %% Copy the P1G release to a directory for use in this testcase ok = copy_installed(Conf,p1g_install,[Master]), - ok = copy_client(Conf,Master,Client,"start_cli1"), + ok = copy_client(Conf,Master,Client,client1), %% start the master node [TestNode] = start_nodes(Conf,[Master],"client1"), @@ -348,7 +355,7 @@ client2(Conf) when is_list(Conf) -> %% Copy the P1G release to a directory for use in this testcase ok = copy_installed(Conf,p1g_install,[Master]), - ok = copy_client(Conf,Master,Client,"start_cli2"), + ok = copy_client(Conf,Master,Client,client2), %% start the master node [TestNode] = start_nodes(Conf,[Master],"client2"), @@ -386,7 +393,7 @@ instructions(Conf) when is_list(Conf) -> {stop, [aa]}, {apply, {?MODULE, no_cc, []}}, {start, [aa]}], - {ok, _} = release_handler_1:eval_script(S1, [], []), + {ok, _} = release_handler_1:eval_script(S1), case whereis(cc) of Pid2 when is_pid(Pid2) -> ok; @@ -396,17 +403,17 @@ instructions(Conf) when is_list(Conf) -> %% Make bb run old version of b. S2 = [point_of_no_return, {remove, {b, soft_purge, soft_purge}}], - {ok, [{b, soft_purge}]} = release_handler_1:eval_script(S2, [], []), + {ok, [{b, soft_purge}]} = release_handler_1:eval_script(S2), check_bstate("first", [FirstBB]), false = code:is_loaded(b), - {error,{old_processes,b}} = release_handler_1:eval_script(S2,[],[]), + {error,{old_processes,b}} = release_handler_1:eval_script(S2), check_bstate("first", [FirstBB]), %% Let supervisor restart bb with new code S3 = [point_of_no_return, {purge, [b]}], - {ok, []} = release_handler_1:eval_script(S3, [], []), + {ok, []} = release_handler_1:eval_script(S3), ok = wait_for(bb), check_bstate("second", []), SecondBB = whereis(bb), @@ -439,7 +446,7 @@ instructions(Conf) when is_list(Conf) -> %% Let supervisor restart bb yet another time S4 = [point_of_no_return, {remove, {b, brutal_purge, soft_purge}}], - {ok, HopefullyEmpty} = release_handler_1:eval_script(S4, [], []), + {ok, HopefullyEmpty} = release_handler_1:eval_script(S4), ok = wait_for(bb), FourthBB = whereis(bb), @@ -559,8 +566,7 @@ otp_2760(Conf) -> %% Execute the relup script and check that app1 is unloaded {ok, [{"after", [{_Rel1Vsn, _Descr, Script}], _}]} = file:consult(filename:join(Rel2Dir, "relup")), - {ok, []} = rpc:call(Node, release_handler_1, eval_script, - [Script, [], []]), + {ok, []} = rpc:call(Node, release_handler_1, eval_script, [Script]), false = rpc:call(Node, code, is_loaded, [app1]), true = stop_node(Node), @@ -651,6 +657,126 @@ otp_5761(Conf) when is_list(Conf) -> true = stop_node(Node), ok. + +%% When a new version of an application is added, but no module is +%% changed - the path was not updated - i.e. code:priv_dir would point +%% to the old location. +otp_9402(Conf) when is_list(Conf) -> + %% Set some paths + PrivDir = priv_dir(Conf), + Dir = filename:join(PrivDir,"otp_9402"), + LibDir = filename:join(?config(data_dir, Conf), "lib"), + + %% Create the releases + Rel1 = create_and_install_fake_first_release(Dir, + [{a,"1.1",LibDir}]), + Rel2 = create_fake_upgrade_release(Dir, + "2", + [{a,"1.2",LibDir}], + {Rel1,Rel1,[LibDir]}), + Rel1Dir = filename:dirname(Rel1), + Rel2Dir = filename:dirname(Rel2), + + %% Start a slave node + {ok, Node} = t_start_node(otp_9402, Rel1, filename:join(Rel1Dir,"sys.config")), + + %% Check path + Dir1 = filename:join([LibDir, "a-1.1"]), + Dir1 = rpc:call(Node, code, lib_dir, [a]), + ABeam = rpc:call(Node, code, which, [a]), + + %% Install second release, with no changed modules + {ok, RelVsn2} = + rpc:call(Node, release_handler, set_unpacked, + [Rel2++".rel", [{a,"1.2",LibDir}]]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "relup")]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "start.boot")]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "sys.config")]), + + {ok, RelVsn1, []} = + rpc:call(Node, release_handler, install_release, [RelVsn2]), + + %% Check path + Dir2 = filename:join([LibDir, "a-1.2"]), + Dir2 = rpc:call(Node, code, lib_dir, [a]), + APrivDir2 = rpc:call(Node, code, priv_dir, [a]), + true = filelib:is_regular(filename:join(APrivDir2,"file")), + + %% Just to make sure no modules have been re-loaded + ABeam = rpc:call(Node, code, which, [a]), + + %% Install RelVsn1 again + {ok, _OtherVsn, []} = + rpc:call(Node, release_handler, install_release, [RelVsn1]), + + %% Check path + Dir1 = rpc:call(Node, code, lib_dir, [a]), + APrivDir1 = rpc:call(Node, code, priv_dir, [a]), + false = filelib:is_regular(filename:join(APrivDir1,"file")), + + %% Just to make sure no modules have been re-loaded + ABeam = rpc:call(Node, code, which, [a]), + + ok. + + +%% When a module is deleted in an appup instruction, the upgrade +%% failed if the module was not loaded. +otp_9417(Conf) when is_list(Conf) -> + %% Set some paths + PrivDir = priv_dir(Conf), + Dir = filename:join(PrivDir,"otp_9417"), + LibDir = filename:join(?config(data_dir, Conf), "lib"), + + %% Create the releases + Rel1 = create_and_install_fake_first_release(Dir, + [{b,"1.0",LibDir}]), + Rel2 = create_fake_upgrade_release(Dir, + "2", + [{b,"2.0",LibDir}], + {Rel1,Rel1,[LibDir]}), + Rel1Dir = filename:dirname(Rel1), + Rel2Dir = filename:dirname(Rel2), + + %% Start a slave node + {ok, Node} = t_start_node(otp_9417, Rel1, filename:join(Rel1Dir,"sys.config")), + + %% Check paths + Dir1 = filename:join([LibDir, "b-1.0"]), + Dir1 = rpc:call(Node, code, lib_dir, [b]), + BLibBeam = filename:join([Dir1,"ebin","b_lib.beam"]), + BLibBeam = rpc:call(Node,code,which,[b_lib]), + false = rpc:call(Node,code,is_loaded,[b_lib]), + false = rpc:call(Node,code,is_loaded,[b_server]), + + %% Install second release, which removes b_lib module + {ok, RelVsn2} = + rpc:call(Node, release_handler, set_unpacked, + [Rel2++".rel", [{b,"2.0",LibDir}]]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "relup")]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "start.boot")]), + ok = rpc:call(Node, release_handler, install_file, + [RelVsn2, filename:join(Rel2Dir, "sys.config")]), + + {ok, _RelVsn1, []} = + rpc:call(Node, release_handler, install_release, [RelVsn2]), + + %% Check that the module does no longer exist + false = rpc:call(Node, code, is_loaded, [b_lib]), + non_existing = rpc:call(Node, code, which, [b_lib]), + + %% And check some paths + Dir2 = filename:join([LibDir, "b-2.0"]), + Dir2 = rpc:call(Node, code, lib_dir, [b]), + BServerBeam = filename:join([Dir2,"ebin","b_server.beam"]), + {file,BServerBeam} = rpc:call(Node,code,is_loaded,[b_server]), + ok. + %% Test upgrade and downgrade of applications eval_appup(Conf) when is_list(Conf) -> @@ -983,19 +1109,16 @@ stop_node(Node) -> ?t:stop_node(Node). -copy_client(Conf,Master,Sname,StartScript) -> +copy_client(Conf,Master,Sname,Client) -> io:format("copy_client(Conf)"), DataDir = ?config(data_dir, Conf), MasterDir = filename:join(priv_dir(Conf),Master), - {ok,Host} = inet:gethostname(), - {ok,IpTuple} = inet:getaddr(Host,inet), - IpAddr = inet_parse:ntoa(IpTuple), - - CliNode = node_name(Sname), + {ClientArgs,RelCliDir} = rh_test_lib:get_client_args(Client,Sname,MasterDir, + node_name(Master)), - Cli = filename:join([MasterDir, "clients", "type1", CliNode]), + Cli = filename:join([MasterDir, RelCliDir]), ok = filelib:ensure_dir(filename:join([Cli,"bin","."])), ok = filelib:ensure_dir(filename:join([Cli,"releases","."])), ok = filelib:ensure_dir(filename:join([Cli,"log","."])), @@ -1003,12 +1126,16 @@ copy_client(Conf,Master,Sname,StartScript) -> P1GOrig = filename:join([MasterDir, "releases", "P1G"]), ok = copy_tree(Conf,P1GOrig,filename:join(Cli,"releases")), - ok = subst_file(filename:join([DataDir, "clients", StartScript]), - filename:join([Cli,"bin","start"]), - [{"ROOT",MasterDir}, - {"MASTER",atom_to_list(Master)}, - {"IPADDR",IpAddr}], - [{chmod,8#0755}]), + case os:type() of + {unix,_} -> + ok = subst_file(filename:join([DataDir, "start_client"]), + filename:join([Cli,"bin","start"]), + [{"ROOT",MasterDir}, + {"CLIENTARGS",ClientArgs}], + [{chmod,8#0755}]); + _ -> + ok + end, StartErlData = filename:join([MasterDir, "releases", "start_erl.data"]), CliRelDir = filename:join([Cli, "releases"]), @@ -1030,21 +1157,32 @@ delete_release(Conf) -> {ok, Dirs} = file:list_dir(PrivDir), ?t:format("======== deleting ~p~n",[Dirs]), - ok = delete_release_os(Dirs), - ?t:format("======== remaining ~p~n",[file:list_dir(PrivDir)]), + ok = delete_release_os(Dirs--["save"]), + {ok,Remaining} = file:list_dir(PrivDir), + ?t:format("======== remaining ~p~n",[Remaining]), + + case Remaining of + [] -> + ok; + _ -> + delete_release_os(Remaining), + Remaining2 = file:list_dir(PrivDir), + ?t:format("======== remaining after second try ~p~n",[Remaining2]) + end, + ok = file:set_cwd(OrigWd), ok. delete_release_os(Dirs) -> case os:type() of - {unix, _} -> - delete_release_unix(Dirs); - {win32, _} -> - delete_release_win32(Dirs); - Os -> - test_server:fail({error, {not_yet_implemented_os, Os}}) - end. + {unix, _} -> + delete_release_unix(Dirs); + {win32, _} -> + delete_release_win32(Dirs); + Os -> + test_server:fail({error, {not_yet_implemented_os, Os}}) + end. delete_release_unix([]) -> @@ -1075,7 +1213,14 @@ delete_release_win32([]) -> delete_release_win32(["save"|Dirs]) -> delete_release_win32(Dirs); delete_release_win32([Dir|Dirs]) -> - Rm = string:concat("rmdir /s ", Dir), + Rm = + case filelib:is_dir(Dir) of + true -> + string:concat("rmdir /s /q ", Dir); + false -> + string:concat("del /q ", Dir) + end, + ?t:format("============== COMMAND ~p~n",[Rm]), [] = os:cmd(Rm), delete_release_win32(Dirs). @@ -1200,7 +1345,12 @@ subst_var([], Vars, Result, VarAcc) -> priv_dir(Conf) -> - filename:absname(?config(priv_dir, Conf)). % Get rid of trailing slash +%% filename:absname(?config(priv_dir, Conf)). % Get rid of trailing slash + %% Due to problem with long paths on windows => creating a new + %% priv_dir under data_dir + Dir = filename:absname(filename:join(?config(data_dir, Conf),priv_dir)), + filelib:ensure_dir(filename:join(Dir,"*")), + Dir. latest_version(Dir) -> List = filelib:wildcard(Dir ++ "*"), @@ -1256,12 +1406,28 @@ do_create_p1g(Conf,TargetDir) -> ErtsLatest = latest_version(filename:join(code:root_dir(),"erts")), ok = copy_tree(Conf, ErtsLatest, ErtsDir, TargetDir), ErtsBinDir = filename:join([TargetDir,ErtsDir,bin]), - copy_file(filename:join([ErtsBinDir, "epmd"]), BinDir, [preserve]), - copy_file(filename:join([ErtsBinDir, "run_erl"]), BinDir, [preserve]), - copy_file(filename:join([ErtsBinDir, "to_erl"]), BinDir, [preserve]), + + case os:type() of + {unix, _} -> + copy_file(filename:join([ErtsBinDir, "epmd"]), BinDir, [preserve]), + copy_file(filename:join([ErtsBinDir, "run_erl"]), BinDir, [preserve]), + copy_file(filename:join([ErtsBinDir, "to_erl"]), BinDir, [preserve]), + + %% Create the start_erl shell script + ok = subst_file(filename:join([ErtsBinDir,"start_erl.src"]), + filename:join([BinDir,"start_erl"]), + [{"EMU","beam"}], + [{chmod,8#0755}]); + {win32,_} -> + %% Add a batch file to use as HEART_COMMAND + ok = copy_file(filename:join(DataDir, "heart_restart.bat"), + ErtsBinDir,[preserve]) + end, copy_file(filename:join(DataDir, "../installer.beam"), filename:join([DataDir,lib,"installer-1.0",ebin])), + copy_file(filename:join(DataDir, "../rh_test_lib.beam"), + filename:join([DataDir,lib,"installer-1.0",ebin])), %% Create .rel, .script and .boot files RelName = "rel0", @@ -1272,7 +1438,7 @@ do_create_p1g(Conf,TargetDir) -> ok = filelib:ensure_dir(RelFile), LibPath = filename:join([DataDir,lib,"*",ebin]), - TarFile = create_basic_release(RelFile, RelVsn, {ErtsVsn,false}, + TarFile = create_basic_release(Conf, RelFile, RelVsn, {ErtsVsn,false}, LibPath, [], [], [], []), %% Extract tar file in target directory (i.e. same directory as erts etc.) @@ -1286,20 +1452,6 @@ do_create_p1g(Conf,TargetDir) -> %% Create RELEASES ok = release_handler:create_RELEASES(TargetDir,ReleasesDir,RelFile,[]), - %% Create start_erl - ok = subst_file(filename:join([ErtsBinDir,"start_erl.src"]), - filename:join([BinDir,"start_erl"]), - [{"EMU","beam"}], - [{chmod,8#0755}]), - - %% Create start script - %% Using a customized start script from DataDir where some options - %% (heart and nodename) are added compared to the start.src in the - %% erlang distribution. - ok = subst_file(filename:join(DataDir, "start"), - filename:join([BinDir, "start"]), - [{"ROOT",TargetDir}], - [preserve]), ok. %% Create version P1H - which is P1G + a-1.0 @@ -1336,12 +1488,12 @@ create_upgrade_release(Conf,RelName,RelVsn,Erts,Apps,Config,{UpFromName,Descr}) UpFrom = [{filename:join([PrivDir,UpFromName,UpFromName]),Descr}], - create_basic_release(RelFile, RelVsn, Erts, LibPath, + create_basic_release(Conf, RelFile, RelVsn, Erts, LibPath, Apps, Config, UpFrom, []), ok. %% Create .rel, .script, .boot, sys.config and tar -create_basic_release(RelFile,RelVsn,{ErtsVsn,ErtsDir},LibPath,ExtraApps,Config,UpFrom,DownTo) -> +create_basic_release(Conf, RelFile,RelVsn,{ErtsVsn,ErtsDir},LibPath,ExtraApps,Config,UpFrom,DownTo) -> RelDir = filename:dirname(RelFile), RelFileName = filename:rootname(RelFile), @@ -1370,7 +1522,14 @@ create_basic_release(RelFile,RelVsn,{ErtsVsn,ErtsDir},LibPath,ExtraApps,Config,U _ -> [{erts,ErtsDir}] end]), - RelFileName ++ ".tar.gz". + TarFileName = RelFileName ++ ".tar.gz", + + case os:type() of + {win32,_} when ErtsDir=/=false -> modify_tar_win32(Conf, TarFileName); + _ -> ok + end, + + TarFileName. %% Create a .rel file create_installer_rel_file(RelFile,RelVsn,ErtsVsn,ExtraApps) -> @@ -1470,21 +1629,70 @@ permanent_p1h(Node) -> copy_installed(Conf,FromNode,ToNodes) -> PrivDir = priv_dir(Conf), DataDir = ?config(data_dir,Conf), + + %% Instead of using copy_tree on the complete node directory, I'm + %% splitting this in separate tar files per subdirectory so the + %% log directory can be completely skipped. The reason for this is + %% that the tar file might become faulty if the node is alive and + %% writing to the log while the tar is created. + FromDir = filename:join(PrivDir,FromNode), + {ok,FromDirNames} = file:list_dir(FromDir), + TempTarFiles = + [begin + TempTarFile = filename:join(PrivDir,"temp_" ++ FDN ++ ".tar"), + {ok,Tar} = erl_tar:open(TempTarFile,[write]), + ok = erl_tar:add(Tar,filename:join(FromDir,FDN),FDN,[]), + ok = erl_tar:close(Tar), + TempTarFile + end || FDN <- FromDirNames, FDN=/="log"], lists:foreach( fun(Node) -> - ok = copy_tree(Conf,filename:join(PrivDir,FromNode),Node,PrivDir), NodeDir = filename:join(PrivDir,Node), - ok = subst_file(filename:join(DataDir, "start"), - filename:join([NodeDir, "bin", "start"]), - [{"ROOT",NodeDir}]), - LogDir = filename:join(NodeDir,log), - {ok,Logs} = file:list_dir(LogDir), - lists:foreach(fun(Log) -> - file:delete(filename:join(LogDir,Log)) - end, - Logs) + ok = filelib:ensure_dir(filename:join([NodeDir,"log","*"])), + lists:foreach( + fun(TempTarFile) -> + ok = erl_tar:extract(TempTarFile,[{cwd,NodeDir}]) + end, TempTarFiles), + case os:type() of + {unix,_} -> + %% Create start script + %% Using a customized start script from DataDir + %% where some options (heart and nodename) are + %% added compared to the start.src in the erlang + %% distribution. + ok = subst_file(filename:join(DataDir, "start"), + filename:join([NodeDir, "bin", "start"]), + [{"ROOT",NodeDir}], + [preserve]); + {win32,_} -> + %% Write erl.ini + ErtsDirs = + filelib:wildcard(filename:join(NodeDir,"erts-*")), + lists:foreach( + fun(ErtsDir) -> + ok = subst_file( + filename:join(DataDir, "erl.ini.src"), + filename:join([ErtsDir, "bin", "erl.ini"]), + [{"ROOTDIR",NodeDir}, + {"BINDIR",filename:join(ErtsDir,"bin")}]) + end, + ErtsDirs), + + %% The service on windows runs as local + %% administrator (not otptest user), so we need + %% to chmod the release in order to allow the + %% executing node to install releases, write + %% logs etc. + chmod_release_win32(NodeDir) + end end, - ToNodes). + ToNodes), + + lists:foreach(fun(TempTarFile) -> file:delete(TempTarFile) end, TempTarFiles), + ok. + +chmod_release_win32(Dir) -> + os:cmd("echo y|cacls " ++ Dir ++ " /T /E /G Administrators:F"). start_nodes(Conf,Snames,Tag) -> PrivDir = priv_dir(Conf), @@ -1493,19 +1701,42 @@ start_nodes(Conf,Snames,Tag) -> fun(Sname) -> NodeDir = filename:join(PrivDir,Sname), Node = node_name(Sname), - - Script = filename:join([NodeDir,"bin","start"]), - Cmd = "env NODENAME="++atom_to_list(Sname) ++ " " ++ Script, - %% {ok,StartFile} = file:read_file(Cmd), - %% io:format("~s:\n~s~n~n",[Start,binary_to_list(StartFile)]), - Res = os:cmd(Cmd), - io:format("Start ~p: ~p~n=>\t~p~n", [Sname,Cmd,Res]), + + case os:type() of + {unix,_} -> + start_node_unix(Sname,NodeDir); + {win32,_} -> + start_node_win32(Sname,NodeDir) + end, Node end, Snames), wait_nodes_up(Nodes,Tag), Nodes. +start_node_unix(Sname,NodeDir) -> + Script = filename:join([NodeDir,"bin","start"]), + Cmd = "env NODENAME="++atom_to_list(Sname) ++ " " ++ Script, + %% {ok,StartFile} = file:read_file(Cmd), + %% io:format("~s:\n~s~n~n",[Start,binary_to_list(StartFile)]), + Res = os:cmd(Cmd), + io:format("Start ~p: ~p~n=>\t~p~n", [Sname,Cmd,Res]). + +start_node_win32(Sname,NodeDir) -> + Name = atom_to_list(Sname) ++ "_P1G", + ErtsBinDir = filename:join(NodeDir,"erts-4.4/bin"), + + StartErlArgs = rh_test_lib:get_start_erl_args(NodeDir), + ServiceArgs = rh_test_lib:get_service_args(NodeDir, Sname, StartErlArgs), + + Erlsrv = filename:nativename(filename:join(ErtsBinDir,"erlsrv")), + rh_test_lib:erlsrv(Erlsrv,stop,Name), + rh_test_lib:erlsrv(Erlsrv,remove,Name), + ok = rh_test_lib:erlsrv(Erlsrv,add,Name,ServiceArgs), + ok = rh_test_lib:erlsrv(Erlsrv,start,Name), + ok. + +%% Create a unique node name for each test case tc_sname(Config) -> tc_sname(Config,""). tc_sname(Config,Fix) when is_atom(Fix) -> @@ -1649,3 +1880,32 @@ create_fake_release(Dir,RelName,RelVsn,AppDirs) -> rpc_inst(Node,Func,Args) -> rpc:call(Node,installer,Func,[node()|Args]). + +delete_all_services() -> + ErlSrv = erlsrv:erlsrv(erlang:system_info(version)), + [_|Serviceinfo] = string:tokens(os:cmd(ErlSrv ++ " list"),"\n"), + Services = + [lists:takewhile(fun($\t) -> false; (_) -> true end,S) + || S <- Serviceinfo], + ?t:format("Services to remove: ~p~n",[Services]), + lists:foreach(fun(S) -> + rh_test_lib:erlsrv(ErlSrv,stop,S), + rh_test_lib:erlsrv(ErlSrv,remove,S) + end, + Services). + +modify_tar_win32(Conf, TarFileName) -> + DataDir = ?config(data_dir,Conf), + PrivDir = priv_dir(Conf), + TmpDir = filename:join(PrivDir,"tmp_modify_tar_win32"), + ok = erl_tar:extract(TarFileName,[{cwd,TmpDir},compressed]), + + ErtsBinDir = filelib:wildcard(filename:join([TmpDir,"erts-*","bin"])), + ok = copy_file(filename:join(DataDir, "heart_restart.bat"), + ErtsBinDir,[preserve]), + + {ok,Fs} = file:list_dir(TmpDir), + {ok,T} = erl_tar:open(TarFileName,[write,compressed]), + [ok = erl_tar:add(T,filename:join(TmpDir,F),F,[]) || F <- Fs], + ok = erl_tar:close(T), + ok. diff --git a/lib/sasl/test/release_handler_SUITE_data/Makefile.src b/lib/sasl/test/release_handler_SUITE_data/Makefile.src index 85e25fdc2f..a12e526d2e 100644 --- a/lib/sasl/test/release_handler_SUITE_data/Makefile.src +++ b/lib/sasl/test/release_handler_SUITE_data/Makefile.src @@ -1,14 +1,19 @@ EFLAGS=+debug_info P2B= \ - P2B/a-2.0/ebin/a.beam \ - P2B/a-2.0/ebin/a_sup.beam + P2B/a-2.0/ebin/a.@EMULATOR@ \ + P2B/a-2.0/ebin/a_sup.@EMULATOR@ LIB= \ - lib/a-1.1/ebin/a.beam \ - lib/a-1.1/ebin/a_sup.beam \ - lib/a-1.0/ebin/a.beam \ - lib/a-1.0/ebin/a_sup.beam \ + lib/a-1.2/ebin/a.@EMULATOR@ \ + lib/a-1.2/ebin/a_sup.@EMULATOR@ \ + lib/a-1.1/ebin/a.@EMULATOR@ \ + lib/a-1.1/ebin/a_sup.@EMULATOR@ \ + lib/a-1.0/ebin/a.@EMULATOR@ \ + lib/a-1.0/ebin/a_sup.@EMULATOR@ \ + lib/b-1.0/ebin/b_server.@EMULATOR@ \ + lib/b-1.0/ebin/b_lib.@EMULATOR@ \ + lib/b-2.0/ebin/b_server.@EMULATOR@ APP= \ app1_app2/lib1/app1-1.0/ebin/app1_sup.@EMULATOR@ \ @@ -57,6 +62,21 @@ lib/a-1.1/ebin/a_sup.@EMULATOR@: lib/a-1.1/src/a_sup.erl erlc $(EFLAGS) -olib/a-1.1/ebin lib/a-1.1/src/a_sup.erl +lib/a-1.2/ebin/a.@EMULATOR@: lib/a-1.2/src/a.erl + erlc $(EFLAGS) -olib/a-1.2/ebin lib/a-1.2/src/a.erl +lib/a-1.2/ebin/a_sup.@EMULATOR@: lib/a-1.2/src/a_sup.erl + erlc $(EFLAGS) -olib/a-1.2/ebin lib/a-1.2/src/a_sup.erl + +lib/b-1.0/ebin/b_server.@EMULATOR@: lib/b-1.0/src/b_server.erl + erlc $(EFLAGS) -olib/b-1.0/ebin lib/b-1.0/src/b_server.erl +lib/b-1.0/ebin/b_lib.@EMULATOR@: lib/b-1.0/src/b_lib.erl + erlc $(EFLAGS) -olib/b-1.0/ebin lib/b-1.0/src/b_lib.erl + +lib/b-2.0/ebin/b_server.@EMULATOR@: lib/b-2.0/src/b_server.erl + erlc $(EFLAGS) -olib/b-2.0/ebin lib/b-2.0/src/b_server.erl + + + app1_app2/lib1/app1-1.0/ebin/app1_sup.@EMULATOR@: app1_app2/lib1/app1-1.0/src/app1_sup.erl erlc $(EFLAGS) -oapp1_app2/lib1/app1-1.0/ebin app1_app2/lib1/app1-1.0/src/app1_sup.erl app1_app2/lib1/app1-1.0/ebin/app1_server.@EMULATOR@: app1_app2/lib1/app1-1.0/src/app1_server.erl diff --git a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1 b/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1 deleted file mode 100755 index ee3d8c97cf..0000000000 --- a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1 +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# This program invokes the erlang emulator by calling run_erl. -# It should only be used at an embedded target system. -# It should be modified to give the correct flags to erl (via start_erl), -# e.g -mode embedded -sname XXX -# -# Usage: start [Data] -# - -if [ "x${NODENAME}" = "x" ] -then - echo "ERROR: Variable \$NODENAME is not set!!" - exit 1 -fi - -TESTHOST=`hostname | sed 's/[.].*//'` -IPADDR=%IPADDR% - -ROOTDIR=%ROOT% -CLIENTDIR=$ROOTDIR/clients/type1/$NODENAME@$TESTHOST - -RELDIR=$CLIENTDIR/releases - -# Note that this scripts is modified an copied to $CLIENTDIR/bin/start -# in release_handler_SUITE:copy_client - therefore HEART_COMMAND is as follows: -HEART_COMMAND=$CLIENTDIR/bin/start -HW_WD_DISABLE=true -export HW_WD_DISABLE HEART_COMMAND - -START_ERL_DATA=${1:-$RELDIR/start_erl.data} - -if [ ! -d /tmp/$NODENAME@$TESTHOST ] -then - mkdir /tmp/$NODENAME@$TESTHOST -fi - -$ROOTDIR/bin/run_erl /tmp/$NODENAME@$TESTHOST/ $CLIENTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME -sasl start_prg \\\"$CLIENTDIR/bin/start\\\" masters \[\\'%MASTER%@$TESTHOST\\'\] client_directory \\\"$CLIENTDIR\\\" -loader inet -id $NODENAME -hosts $IPADDR" > $CLIENTDIR/log/run_erl.out 2>&1 & diff --git a/lib/sasl/test/release_handler_SUITE_data/erl.ini.src b/lib/sasl/test/release_handler_SUITE_data/erl.ini.src new file mode 100644 index 0000000000..b8791e75a5 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/erl.ini.src @@ -0,0 +1,4 @@ +[erlang] +Bindir=%BINDIR% +Progname=erl +Rootdir=%ROOTDIR% diff --git a/lib/sasl/test/release_handler_SUITE_data/heart_restart.bat b/lib/sasl/test/release_handler_SUITE_data/heart_restart.bat new file mode 100755 index 0000000000..ede1ad4ff3 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/heart_restart.bat @@ -0,0 +1,3 @@ +@echo off +%ERLSRV_EXECUTABLE% stop %ERLSRV_SERVICE_NAME% +%ERLSRV_EXECUTABLE% start %ERLSRV_SERVICE_NAME%
\ No newline at end of file diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/README b/lib/sasl/test/release_handler_SUITE_data/lib/README new file mode 100644 index 0000000000..667d21d4cf --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/README @@ -0,0 +1,16 @@ +a-1.0: +start version + +a-1.1: +can be upgraded to from a-1.0. Module a has changed + +a-1.2: +can be upgraded to from a-1.1. +No module have changed, but priv dir is added including one 'file' + +b-1.0: +start version, includes b_lib and b_server + +b-2.0: +can be upgraded to from b-1.0. +Removes b_lib and updates b_server diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/ebin/a.app index e938137f67..b38722f06d 100644 --- a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app +++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/ebin/a.app @@ -1,7 +1,7 @@ {application, a, [{description, "A CXC 138 11"}, - {vsn, "1.0"}, - {modules, [{a, 1}, {a_sup,1}]}, + {vsn, "1.2"}, + {modules, [{a, 2}, {a_sup,1}]}, {registered, [a_sup]}, {applications, [kernel, stdlib]}, {env, [{key1, val1}]}, diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/ebin/a.appup b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/ebin/a.appup new file mode 100644 index 0000000000..3df0546316 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/ebin/a.appup @@ -0,0 +1,3 @@ +{"1.2", + [{"1.1",[]}], + [{"1.1",[]}]}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/priv/file b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/priv/file new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/priv/file diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a.erl new file mode 100644 index 0000000000..c082ad5339 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a.erl @@ -0,0 +1,54 @@ +%% ``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 via the world wide web 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. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id$ +%% +-module(a). + + +-behaviour(gen_server). + +%% External exports +-export([start_link/0, a/0, b/0]). +%% Internal exports +-export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). + +start_link() -> gen_server:start_link({local, aa}, a, [], []). + +a() -> gen_server:call(aa, a). +b() -> gen_server:call(aa, b). + +%%----------------------------------------------------------------- +%% Callback functions from gen_server +%%----------------------------------------------------------------- +init([]) -> + process_flag(trap_exit, true), + {ok, {state, bval}}. + +handle_call(a, _From, State) -> + X = application:get_all_env(a), + {reply, X, State}; + +handle_call(b, _From, State) -> + {reply, {ok, element(2, State)}, State}. + +handle_info(_, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(1, Extra, State) -> + {ok, {state, bval}}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a_sup.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a_sup.erl new file mode 100644 index 0000000000..a141c1767b --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.2/src/a_sup.erl @@ -0,0 +1,37 @@ +%% ``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 via the world wide web 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. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id$ +%% +-module(a_sup). + + +-behaviour(supervisor). + +%% External exports +-export([start/2]). + +%% Internal exports +-export([init/1]). + +start(_, _) -> + supervisor:start_link({local, a_sup}, a_sup, []). + +init([]) -> + SupFlags = {one_for_one, 4, 3600}, + Config = {a, + {a, start_link, []}, + permanent, 2000, worker, [a]}, + {ok, {SupFlags, [Config]}}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/ebin/b.app b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/ebin/b.app new file mode 100644 index 0000000000..00347b2754 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/ebin/b.app @@ -0,0 +1,7 @@ +%% -*- erlang -*- +{application, b, + [{description, "B CXC 138 12"}, + {vsn, "1.0"}, + {modules, [{b_server, 1},{b_lib, 1}]}, + {registered, [b_server]}, + {applications, [kernel, stdlib]}]}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_lib.erl b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_lib.erl new file mode 100644 index 0000000000..7e8a308a5e --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_lib.erl @@ -0,0 +1,3 @@ +-module(b_lib). +-compile(export_all). +foo() -> ok. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_server.erl b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_server.erl new file mode 100644 index 0000000000..e1a80a076f --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-1.0/src/b_server.erl @@ -0,0 +1,37 @@ +-module(b_server). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {}). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(OldVsn, State, Extra) -> + file:write_file("/tmp/b_server",io_lib:format("~p~n",[{"1.0",OldVsn,Extra}])), + {ok, State}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.app b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.app new file mode 100644 index 0000000000..73c8e42b32 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.app @@ -0,0 +1,7 @@ +%% -*- erlang -*- +{application, b, + [{description, "B CXC 138 12"}, + {vsn, "2.0"}, + {modules, [{b_server, 1}]}, + {registered, [b_server]}, + {applications, [kernel, stdlib]}]}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.appup b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.appup new file mode 100644 index 0000000000..d261a37732 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/ebin/b.appup @@ -0,0 +1,3 @@ +{"2.0", + [{"1.0",[{delete_module,b_lib}, {update,b_server,{advanced,[]}}]}], + [{"1.0",[{add_module,b_lib},{update,b_server,{advanced,[]}}]}]}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_lib.erl b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_lib.erl new file mode 100644 index 0000000000..7e8a308a5e --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_lib.erl @@ -0,0 +1,3 @@ +-module(b_lib). +-compile(export_all). +foo() -> ok. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_server.erl b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_server.erl new file mode 100644 index 0000000000..f8bfbdaff7 --- /dev/null +++ b/lib/sasl/test/release_handler_SUITE_data/lib/b-2.0/src/b_server.erl @@ -0,0 +1,37 @@ +-module(b_server). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {}). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(OldVsn, State, Extra) -> + file:write_file("/tmp/b_server",io_lib:format("~p~n",[{"2.0",OldVsn,Extra}])), + {ok, State}. diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app index 6f77317f6a..e1391c0605 100644 --- a/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app +++ b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app @@ -1,6 +1,6 @@ {application, installer, [{description, "Installer application"}, {vsn, "1.0"}, - {modules, [{installer, 1}]}, + {modules, [installer,rh_test_lib]}, {registered, []}, {applications, [kernel, stdlib, sasl]}]}. diff --git a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli2 b/lib/sasl/test/release_handler_SUITE_data/start_client index 88912cf884..5ea94d6f7c 100755 --- a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli2 +++ b/lib/sasl/test/release_handler_SUITE_data/start_client @@ -34,4 +34,4 @@ then mkdir /tmp/$NODENAME@$TESTHOST fi -$ROOTDIR/bin/run_erl /tmp/$NODENAME@$TESTHOST/ $CLIENTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME -sasl start_prg \\\"$CLIENTDIR/bin/start\\\" masters \[\\'%MASTER%@$TESTHOST\\',\\'master2@$TESTHOST\\'\] client_directory \\\"$CLIENTDIR\\\"" > /dev/null 2>&1 & +$ROOTDIR/bin/run_erl /tmp/$NODENAME@$TESTHOST/ $CLIENTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME %CLIENTARGS%" > $CLIENTDIR/log/run_erl.out 2>&1 & diff --git a/lib/sasl/test/rh_test_lib.erl b/lib/sasl/test/rh_test_lib.erl new file mode 100644 index 0000000000..99a7f919a7 --- /dev/null +++ b/lib/sasl/test/rh_test_lib.erl @@ -0,0 +1,100 @@ +-module(rh_test_lib). + +-export([erlsrv/3, + erlsrv/4]). +-export([get_service_args/3, + get_service_args/4, + get_start_erl_args/1, + get_start_erl_args/3, + get_client_args/3, + get_client_args/4]). + + +erlsrv(Erlsrv,Action,Name) -> + erlsrv(Erlsrv,Action,Name,""). +erlsrv(Erlsrv,Action,Name,Rest) -> + Cmd = Erlsrv ++ " " ++ atom_to_list(Action) ++ " " ++ Name ++ " " ++ Rest, + io:format("erlsrv cmd: ~p~n",[Cmd]), + Port = open_port({spawn, Cmd}, [stream, {line, 100}, eof, in]), + Res = recv_prog_output(Port), + case Res of + [] -> + failed; + _Y -> + io:format("erlsrv res: ~p~n",[_Y]), + ok + end. + +recv_prog_output(Port) -> + receive + {Port, {data, {eol,Data}}} -> + %%io:format("Got data: ~s~n", [Data]), + [ Data, "\n" | recv_prog_output(Port)]; + {Port, {data, {noeol,Data}}} -> + %%io:format("Got data: ~s~n", [Data]), + [ Data | recv_prog_output(Port)]; + {Port, _Other} -> + %%io:format("Got ~p from port~n", [_Other]), + Port ! {self(), close}, + receive + {Port,closed} -> + [] + end + end. + +get_service_args(RootDir, Sname, StartErlArgs) -> + get_service_args(RootDir, "", Sname, StartErlArgs). +get_service_args(RootDir, RelClientDir, Sname, StartErlArgs) -> + LogDir = filename:nativename(filename:join([RootDir,RelClientDir,"log"])), + %% start_erl.exe will be found since it is in the same directory as erlsrv.exe + %% And heart_restart.bat will be found since the erts bin dir is + %% always in the path for the erlang virtual machine. + " -machine start_erl.exe -workdir " ++ LogDir ++ + " -debugtype new -sname " ++ atom_to_list(Sname) ++ + " -env HEART_COMMAND=heart_restart.bat -args \"" ++ StartErlArgs ++ "\"". + +get_start_erl_args(RootDir) -> + get_start_erl_args(RootDir,"",""). +get_start_erl_args(RootDir,RelClientDir,ExtraArgs) -> + Cookie = atom_to_list(erlang:get_cookie()), + RelDir = filename:join([RootDir,RelClientDir,"releases"]), + ExtraArgs ++ " -setcookie " ++ Cookie ++ + " -heart ++ -rootdir " ++ filename:nativename(RootDir) ++ + " -reldir " ++ filename:nativename(RelDir). + +%% Must be called on the master node +get_client_args(Client,Sname,RootDir) -> + get_client_args(Client,Sname,RootDir,node()). +get_client_args(Client,Sname,RootDir,Master) -> + {ok,Host} = inet:gethostname(), + Node = atom_to_list(Sname) ++ "@" ++ Host, + RelClientDir = filename:join(["clients","type1",Node]), + ClientDir = filename:join([RootDir,RelClientDir]), + StartPrg = filename:join([ClientDir,"bin","start"]), + {" -sasl start_prg \\\\\\\"" ++ StartPrg ++ "\\\\\\\" masters \[" ++ + single_quote() ++ atom_to_list(Master) ++ single_quote() ++ + get_client_extra_master(Client,Host) ++ + "\] client_directory \\\\\\\"" ++ ClientDir ++ "\\\\\\\"" ++ + get_client_loader_args(Client,Sname,Host), + RelClientDir}. + +get_client_loader_args(client1,Sname,Host) -> + {ok,IpTuple} = inet:getaddr(Host,inet), + IpAddr = inet_parse:ntoa(IpTuple), + " -loader inet -id " ++ + atom_to_list(Sname) ++ " -hosts " ++ IpAddr; +get_client_loader_args(_,_,_) -> + "". + +get_client_extra_master(client2,Host) -> + "," ++ single_quote() ++ "master2@" ++ Host ++ single_quote(); +get_client_extra_master(_,_) -> + "". + +single_quote() -> + case os:type() of + {win32,_} -> + "\'"; + _ -> + "\\'" + end. diff --git a/lib/snmp/Makefile b/lib/snmp/Makefile index 20e3d4692a..c55eff04c6 100644 --- a/lib/snmp/Makefile +++ b/lib/snmp/Makefile @@ -67,7 +67,7 @@ do_configure: configure configure: configure.in autoconf -.PHONY: info +.PHONY: info gclean info: @echo "OS: $(OS)" @@ -76,6 +76,9 @@ info: @echo "SNMP_VSN: $(SNMP_VSN)" @echo "APP_VSN: $(APP_VSN)" +gclean: + git clean -fXd + # ---------------------------------------------------- # Application (source) release targets diff --git a/lib/snmp/doc/src/Makefile b/lib/snmp/doc/src/Makefile index 35ed63e103..aa9431477c 100644 --- a/lib/snmp/doc/src/Makefile +++ b/lib/snmp/doc/src/Makefile @@ -152,6 +152,7 @@ $(TOP_PDF_FILE): $(XML_FILES) pdf: $(TOP_PDF_FILE) html: gifs $(HTML_REF_MAN_FILE) +html2: html $(INDEX_TARGET) clean clean_docs: clean_html clean_man clean_pdf rm -f errs core *~ @@ -228,6 +229,7 @@ clean_man: clean_html: @echo "cleaning html:" rm -rf $(HTMLDIR)/* + rm -f $(INDEX_TARGET) $(MAN7DIR)/%.7: $(MIBSDIR)/%.mib @echo "processing $*" @@ -286,7 +288,7 @@ release_docs_spec: docs $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) $(INSTALL_DIR) $(RELEASE_PATH)/man/man1 $(INSTALL_DATA) $(MAN1_FILES) $(RELEASE_PATH)/man/man1 - $(INSTALL_DIR) $(RELEASE_PATH)/man/man + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 $(INSTALL_DATA) $(MAN3_FILES) $(RELEASE_PATH)/man/man3 $(INSTALL_DIR) $(RELEASE_PATH)/man/man6 $(INSTALL_DATA) $(MAN6_FILES) $(RELEASE_PATH)/man/man6 diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml index 6a20d8ee3a..4178192120 100644 --- a/lib/snmp/doc/src/notes.xml +++ b/lib/snmp/doc/src/notes.xml @@ -33,6 +33,136 @@ </header> <section> + <title>SNMP Development Toolkit 4.21</title> + <p>Version 4.21 supports code replacement in runtime from/to + version 4.20.1, 4.20 and 4.19. </p> + + <section> + <title>Improvements and new features</title> +<!-- + <p>-</p> +--> + <list type="bulleted"> + <item> + <p>[manager] There was no way to specify transport domain. + The transport domains was assumed to be IPv4 (transportDomainUdpIpv4). + This has now been changed so that it can also be IPv6 + (transportDomainUdpIpv6). + To facilitate this, the transport domain, <c>tdomain</c>, + is now a (new) valid option when + <seealso marker="snmpm#register_agent">registering</seealso> + a new agent (and + <seealso marker="snmpm#update_agent_info">updating</seealso> + agent info). </p> + <p>This also mean that the transport behaviour has changed. </p> + <p>Own Id: OTP-9305</p> + <p>Aux Id: Seq 11847</p> + </item> + + <item> + <p>[compiler] Added the option + <seealso marker="snmpc#compile">warnings_as_errors</seealso> + (for the SNMP MIB compiler (escript) frontend, the option + <seealso marker="snmpc(command)#option_wae">--wae</seealso> is used) + which specifies whether warnings should be treated as errors. </p> + <p>Tuncer Ayaz</p> + <p>Own Id: OTP-9437</p> + </item> + </list> + + </section> + + <section> + <title>Fixed Bugs and Malfunctions</title> +<!-- + <p>-</p> +--> + + <list type="bulleted"> + <item> + <p>The snmp config tool could not handle (manager) audit trail config + because the option seqno was not handled. </p> + <p>Own Id: OTP-9354</p> + </item> + + <item> + <p>[agent] The SNMP ACM cache was not properly updated when + changes where made to the VACM security-to-group, access and + view-tree-family tables. </p> + <p>Own Id: OTP-9367</p> + <p>Aux Id: Seq 11858</p> + </item> + + <item> + <p>Fixed install directory typo for man3. </p> + <p>Peter Lemenkov</p> + <p>Hans Ulrich Niedermann</p> + <p>Own Id: OTP-9442</p> + </item> + + </list> + </section> + + + <section> + <title>Incompatibilities</title> + <p>-</p> + </section> + + </section> <!-- 4.21 --> + + + <section> + <title>SNMP Development Toolkit 4.20.1</title> + <p>Version 4.20.1 supports code replacement in runtime from/to + version 4.20, 4.19 and 4.18.</p> + + <section> + <title>Improvements and new features</title> + <p>-</p> +<!-- + <list type="bulleted"> + <item> + <p>Added type specs for functions that do not return. </p> + <p>Kostis Sagonas</p> + <p>Own Id: OTP-9208</p> + </item> + </list> +--> + </section> + + <section> + <title>Fixed Bugs and Malfunctions</title> +<!-- + <p>-</p> +--> + <list type="bulleted"> + <item> + <p>[agent] Did not handle transport domains properly in some cases, + for instance trap sending. </p> + <p>Own Id: OTP-9400</p> + </item> + + <item> + <p>[agent] Wrong default transport domain, snmpUDPDomain, instead + of transportDomainUdpIpv4. </p> + <p>Own Id: OTP-9425</p> + <p>Aux Id: Seq 11874</p> + </item> + + </list> + </section> + + + <section> + <title>Incompatibilities</title> + <p>-</p> + </section> + + </section> <!-- 4.20.1 --> + + + <section> <title>SNMP Development Toolkit 4.20</title> <p>Version 4.20 supports code replacement in runtime from/to version 4.19 and 4.18.</p> diff --git a/lib/snmp/doc/src/snmpc.xml b/lib/snmp/doc/src/snmpc.xml index 771629492d..61d19251c5 100644 --- a/lib/snmp/doc/src/snmpc.xml +++ b/lib/snmp/doc/src/snmpc.xml @@ -48,7 +48,11 @@ <type> <v>File = string()</v> <v>Options = [opt()]</v> - <v>opt() = db() | relaxed_row_name_assign_check() | deprecated() | description() | reference() | group_check() | i() | il() | imports() | module() | module_identity() | module_compliance() | agent_capabilities() | outdir() | no_defs() | verbosity() | warnings()</v> + <v>opt() = db() | relaxed_row_name_assign_check() | deprecated() | + description() | reference() | group_check() | i() | il() | + imports() | module() | module_identity() | module_compliance() | + agent_capabilities() | outdir() | no_defs() | verbosity() | + warnings() | warnings_as_errors()</v> <v>db() = {db, volatile|persistent|mnesia}</v> <v>deprecated() = {deprecated, bool()}</v> <v>relaxed_row_name_assign_check() = relaxed_row_name_assign_check</v> @@ -66,6 +70,7 @@ <v>outdir() = {outdir, dir()}</v> <v>verbosity() = {verbosity, silence|warning|info|log|debug|trace}</v> <v>warnings() = {warnings, bool()}</v> + <v>warnings_as_errors() = warnings_as_errors</v> <v>dir() = string()</v> <v>BinFileName = string()</v> </type> @@ -200,11 +205,17 @@ <item> <p>The option <c>warnings</c> specifies whether warning - messages should be shown. </p> + messages should be shown. </p> <p>Default is <c>true</c>. </p> </item> + <item> + <p>The option <c>warnings_as_errors</c>, if present, specifies + whether warnings should be treated as errors.</p> + </item> + </list> + <p>The MIB compiler understands both SMIv1 and SMIv2 MIBs. It uses the <c>MODULE-IDENTITY</c> statement to determine if the MIB is version 1 or 2. diff --git a/lib/snmp/doc/src/snmpc_cmd.xml b/lib/snmp/doc/src/snmpc_cmd.xml index 9358382a10..72116f8981 100644 --- a/lib/snmp/doc/src/snmpc_cmd.xml +++ b/lib/snmp/doc/src/snmpc_cmd.xml @@ -50,6 +50,8 @@ with definitions of Erlang constants for the objects in the MIB, see <seealso marker="snmpc#mib_to_hrl">mib_to_hrl/1</seealso>. </p> + + <marker id="options"></marker> </desc> </func> </funcs> @@ -58,15 +60,18 @@ <title>Compiler options</title> <p>The following options are supported (note that most of these relate to the compilation of the MIB file):</p> + <marker id="option_help"></marker> <taglist> <tag>--help</tag> <item> <p>Prints help info.</p> + <marker id="option_version"></marker> </item> <tag>--version</tag> <item> <p>Prints application and mib format version.</p> + <marker id="option_verbosity"></marker> </item> <tag>--verbosity <em>verbosity</em></tag> @@ -74,11 +79,20 @@ <p>Print debug info. </p> <p><c>verbosity</c> = <c>trace</c> | <c>debug</c> | <c>log</c> | <c>info</c> | <c>silence</c></p> <p>Defaults to <c>silence</c>.</p> + <marker id="option_warnings"></marker> </item> <tag>--warnings</tag> <item> <p>Print warning messages. </p> + <marker id="option_wae"></marker> + </item> + + <tag>--wae</tag> + <item> + <p>Warnings as errors. + Indicates that warnings shall be treated as errors. </p> + <marker id="option_odir"></marker> </item> <tag>--o <em>directory</em></tag> @@ -86,6 +100,7 @@ <p>The directory where the compiler should place the output files. If not specified, output files will be placed in the current working directory.</p> + <marker id="option_idir"></marker> </item> <tag>--i <em>Directory</em></tag> @@ -94,6 +109,7 @@ By default, the current working directory is always included. </p> <p>This option can be present several times, each time specifying <em>one</em> path. </p> + <marker id="option_ildir"></marker> </item> <tag>--il <em>Directory</em></tag> @@ -106,6 +122,7 @@ the current version may be in the system). The current directory and the "snmp-home"/priv/mibs/ are always listed last in the include path. </p> + <marker id="option_sgc"></marker> </item> <tag>--sgc</tag> @@ -114,42 +131,50 @@ group check of the mib compiler. That is, should the OBJECT-GROUP and the NOTIFICATION-GROUP macro(s) be checked for correctness or not. </p> + <marker id="option_dep"></marker> </item> <tag>--dep</tag> <item> <p>Keep deprecated definition(s). If not specified the compiler will ignore deprecated definitions. </p> + <marker id="option_desc"></marker> </item> <tag>--desc</tag> <item> <p>The DESCRIPTION field will be included. </p> + <marker id="option_ref"></marker> </item> <tag>--ref</tag> <item> <p>The REFERENCE field will be included. </p> + <marker id="option_imp"></marker> </item> <tag>--imp</tag> <item> <p>The IMPORTS field will be included. </p> + <marker id="option_mi"></marker> </item> <tag>--mi</tag> <item> <p>The MODULE-IDENTITY field will be included. </p> + <marker id="option_mc"></marker> </item> <tag>--mc</tag> <item> <p>The MODULE-COMPLIANCE field will be included. </p> + <marker id="option_ac"></marker> </item> <tag>--ac</tag> <item> <p>The AGENT-CAPABILITIES field will be included. </p> + <marker id="option_mod"></marker> </item> <tag>--mod <em>module</em></tag> @@ -157,6 +182,7 @@ <p>The module which implements all the instrumentation functions. </p> <p>The name of all instrumentation functions must be the same as the corresponding managed object it implements. </p> + <marker id="option_nd"></marker> </item> <tag>--nd</tag> @@ -165,6 +191,7 @@ used if a managed object have no instrumentation function. Instead this will be reported as an error, and the compilation aborts. </p> + <marker id="option_rrnac"></marker> </item> <tag>--rrnac</tag> @@ -176,6 +203,7 @@ This means that the error will be converted to a warning. </p> <p>By default it is not included, but if this option is present it will be. </p> + <marker id="see_also"></marker> </item> </taglist> diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml index b527d171b0..c36a1b2a24 100644 --- a/lib/snmp/doc/src/snmpm.xml +++ b/lib/snmp/doc/src/snmpm.xml @@ -283,27 +283,27 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv <v>TargetName = target_name()</v> <v>Config = [agent_config()]</v> <v>agent_config() = {Item, Val}</v> - <v>Item = engine_id | address | port | community | timeout | max_message_size | version | sec_model | sec_name | sec_level</v> + <v>Item = engine_id | address | port | community | timeout | max_message_size | version | sec_model | sec_name | sec_level | tdomain</v> <v>Val = term()</v> <v>Reason = term()</v> </type> <desc> <p>Explicitly instruct the manager to handle this agent, with - <c>UserId</c> as the responsible user. </p> - <p>Called to instruct the manager that this agent - shall be handled. This function is used when - the user knows in advance which agents the - manager shall handle. - Note that there is an alternate way to do the same thing: - Add the agent to the manager config files (see - <seealso marker="snmp_manager_config_files#agents">agents.conf</seealso>).</p> + <c>UserId</c> as the responsible user. </p> + <p>Called to instruct the manager that this agent shall be handled. + This function is used when the user knows in advance which agents + the manager shall handle. + Note that there is an alternate way to do the same thing: + Add the agent to the manager config files (see + <seealso marker="snmp_manager_config_files#agents">agents.conf</seealso>).</p> <p><c>TargetName</c> is a non-empty string, - uniquely identifying the agent. </p> - <p>The type of <c>Val</c> depends on <c>Item</c>: </p> + uniquely identifying the agent. </p> + <p>The type of <c>Val</c> depends on <c>Item</c>: </p> <code type="none"><![CDATA[ [mandatory] engine_id = string() [mandatory] address = ip_address() [optional] port = integer() +[optional] tdomain = transportDomainUdpIpv4 | transportDomainUdpIpv6 [optional] community = string() [optional] timeout = integer() | snmp_timer() [optional] max_message_size = integer() @@ -312,7 +312,9 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv [optional] sec_name = string() [optional] sec_level = noAuthNoPriv | authNoPriv | authPriv ]]></code> - <p>Note that if no <c>Port</c> is given, the default value is used.</p> + <p>Note that if no <c>tdomain</c> is given, the default value, + <c>transportDomainUdpIpv4</c>, is used.</p> + <p>Note that if no <c>port</c> is given, the default value is used.</p> <marker id="unregister_agent"></marker> </desc> @@ -348,17 +350,25 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> + <name>update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name> <name>update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name> <fsummary>Update agent config</fsummary> <type> <v>UserId = term()</v> <v>TargetName = target_name()</v> - <v>Item = atom()</v> - <v>Val = term()</v> + <v>Info = [{item(), item_value()}]</v> + <v>Item = item()</v> + <v>item() = atom()</v> + <v>Val = item_value()</v> + <v>item_value() = term()</v> <v>Reason = term()</v> </type> <desc> - <p>Update agent config.</p> + <p>Update agent config. The function <c>update_agent_info/3</c> + should be used when several values needs to be updated atomically. </p> + <p>See function + <seealso marker="#register_agent">register_agent</seealso>) + for more info about what kind of items are allowed. </p> <marker id="which_agents"></marker> </desc> diff --git a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl index 28469a7b4e..37f6dd3f26 100644 --- a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl +++ b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl @@ -247,6 +247,7 @@ add_sec2group(SecModel, SecName, GroupName) -> Key = [Key1, length(Key2) | Key2], case table_cre_row(vacmSecurityToGroupTable, Key, Row) of true -> + snmpa_agent:invalidate_ca_cache(), {ok, Key}; false -> {error, create_failed} @@ -260,6 +261,7 @@ add_sec2group(SecModel, SecName, GroupName) -> delete_sec2group(Key) -> case table_del_row(vacmSecurityToGroupTable, Key) of true -> + snmpa_agent:invalidate_ca_cache(), ok; false -> {error, delete_failed} @@ -279,6 +281,7 @@ add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Key3 = [SM, SL], Key = Key1 ++ Key2 ++ Key3, snmpa_vacm:insert([{Key, Row}], false), + snmpa_agent:invalidate_ca_cache(), {ok, Key}; {error, Reason} -> {error, Reason}; @@ -287,6 +290,7 @@ add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> end. delete_access(Key) -> + snmpa_agent:invalidate_ca_cache(), snmpa_vacm:delete(Key). @@ -299,6 +303,7 @@ add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Key = [length(Key1) | Key1] ++ [length(Key2) | Key2], case table_cre_row(vacmViewTreeFamilyTable, Key, Row) of true -> + snmpa_agent:invalidate_ca_cache(), {ok, Key}; false -> {error, create_failed} @@ -312,6 +317,7 @@ add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> delete_view_tree_fam(Key) -> case table_del_row(vacmViewTreeFamilyTable, Key) of true -> + snmpa_agent:invalidate_ca_cache(), ok; false -> {error, delete_failed} diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 82a7ec647b..6322f0f21d 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -1626,7 +1626,7 @@ invalidate_ca_cache() -> MasterAgent ! invalidate_ca_cache; false -> %% This is running on a sub-agent node, - %% so sent skip it + %% so skip it ok end; _ -> % Not on this node diff --git a/lib/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl index 4b88eb69f7..c17a6abbd7 100644 --- a/lib/snmp/src/agent/snmpa_conf.erl +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -424,7 +424,8 @@ target_addr_entry(Name, EngineId, TMask) -> target_addr_entry(Name, Ip, 162, TagList, - ParamsName, EngineId, TMask, 2048). + ParamsName, EngineId, + TMask, 2048). target_addr_entry(Name, Ip, @@ -435,7 +436,8 @@ target_addr_entry(Name, TMask, MaxMessageSize) -> target_addr_entry(Name, Ip, Udp, 1500, 3, TagList, - ParamsName, EngineId, TMask, MaxMessageSize). + ParamsName, EngineId, + TMask, MaxMessageSize). target_addr_entry(Name, Ip, @@ -448,7 +450,8 @@ target_addr_entry(Name, TMask, MaxMessageSize) -> target_addr_entry(Name, snmp_target_mib:default_domain(), Ip, Udp, - Timeout, RetryCount, TagList, ParamsName, + Timeout, RetryCount, TagList, + ParamsName, EngineId, TMask, MaxMessageSize). target_addr_entry(Name, diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl index 14f62b12f3..4f50b1a674 100644 --- a/lib/snmp/src/agent/snmpa_mpd.erl +++ b/lib/snmp/src/agent/snmpa_mpd.erl @@ -32,6 +32,7 @@ -include("SNMP-MPD-MIB.hrl"). -include("SNMPv2-TM.hrl"). -include("SNMP-FRAMEWORK-MIB.hrl"). +-include("TRANSPORT-ADDRESS-MIB.hrl"). -define(VMODULE,"MPD"). -include("snmp_verbosity.hrl"). @@ -981,12 +982,15 @@ generate_discovery_msg2(NoteStore, Pdu, discovery_note_timeout(Timeout) -> (Timeout div 100) + 1. -generate_discovery_msg(NoteStore, {?snmpUDPDomain, [A,B,C,D,U1,U2]}, +generate_discovery_msg(NoteStore, {TDomain, TAddress}, Pdu, ScopedPduBytes, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout) -> + + {ok, {_Domain, Address}} = transform_taddr(TDomain, TAddress), + %% 7.1.7 ?vdebug("generate_discovery_msg -> 7.1.7 (~w)", [ManagerEngineID]), MsgID = generate_msg_id(), @@ -1027,7 +1031,7 @@ generate_discovery_msg(NoteStore, {?snmpUDPDomain, [A,B,C,D,U1,U2]}, %% Log(Packet), inc_snmp_out_vars(Pdu), ?vdebug("generate_discovery_msg -> done", []), - {Packet, {{A,B,C,D}, U1 bsl 8 + U2}}; + {Packet, Address}; Error -> throw(Error) @@ -1057,6 +1061,34 @@ generate_sec_discovery_msg(Message, SecModule, end. +transform_taddr(?snmpUDPDomain, TAddress) -> + transform_taddr(?transportDomainUdpIpv4, TAddress); +transform_taddr(?transportDomainUdpIpv4, [A, B, C, D, P1, P2]) -> + Domain = transportDomainUdpIpv4, + Addr = {A,B,C,D}, + Port = P1 bsl 8 + P2, + Address = {Addr, Port}, + {ok, {Domain, Address}}; +transform_taddr(?transportDomainUdpIpv4, BadAddr) -> + {error, {bad_transportDomainUdpIpv4_address, BadAddr}}; +transform_taddr(?transportDomainUdpIpv6, + [A1, A2, A3, A4, A5, A6, A7, A8, P1, P2]) -> + Domain = transportDomainUdpIpv6, + Addr = {A1, A2, A3, A4, A5, A6, A7, A8}, + Port = P1 bsl 8 + P2, + Address = {Addr, Port}, + {ok, {Domain, Address}}; +transform_taddr(?transportDomainUdpIpv6, BadAddr) -> + {error, {bad_transportDomainUdpIpv6_address, BadAddr}}; +transform_taddr(BadTDomain, TAddress) -> + case lists:member(BadTDomain, snmp_conf:all_tdomains()) of + true -> + {error, {unsupported_tdomain, BadTDomain, TAddress}}; + false -> + {error, {unknown_tdomain, BadTDomain, TAddress}} + end. + + process_taddrs(Dests) -> ?vtrace("process_taddrs -> entry with" "~n Dests: ~p", [Dests]), @@ -1066,46 +1098,44 @@ process_taddrs([], Acc) -> ?vtrace("process_taddrs -> entry when done with" "~n Acc: ~p", [Acc]), lists:reverse(Acc); - + %% v3 -process_taddrs([{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], Acc) -> +process_taddrs([{{TDomain, TAddress}, SecData} | T], Acc) -> ?vtrace("process_taddrs -> entry when v3 with" - "~n A: ~p" - "~n B: ~p" - "~n C: ~p" - "~n D: ~p" - "~n U1: ~p" - "~n U2: ~p" - "~n SecData: ~p", [A, B, C, D, U1, U2, SecData]), - Entry = {{snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, SecData}, - process_taddrs(T, [Entry | Acc]); -%% Bad v3 -process_taddrs([{{TDomain, TAddr}, _SecData} | T], Acc) -> - ?vtrace("process_taddrs -> entry when bad v3 with" - "~n TDomain: ~p" - "~n TAddr: ~p", [TDomain, TAddr]), - user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), - process_taddrs(T, Acc); + "~n TDomain: ~p" + "~n TAddress: ~p" + "~n SecData: ~p", [TDomain, TAddress, SecData]), + case transform_taddr(TDomain, TAddress) of + {ok, DestAddr} -> + ?vtrace("process_taddrs -> transformed: " + "~n DestAddr: ~p", [DestAddr]), + Entry = {DestAddr, SecData}, + process_taddrs(T, [Entry | Acc]); + {error, Reason} -> + ?vinfo("Failed transforming v3 domain and address" + "~n Reason: ~p", [Reason]), + user_err("Bad TDomain/TAddress: ~w/~w", [TDomain, TAddress]), + process_taddrs(T, Acc) + end; %% v1 & v2 -process_taddrs([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T], Acc) -> +process_taddrs([{TDomain, TAddress} | T], Acc) -> ?vtrace("process_taddrs -> entry when v1/v2 with" - "~n A: ~p" - "~n B: ~p" - "~n C: ~p" - "~n D: ~p" - "~n U1: ~p" - "~n U2: ~p", [A, B, C, D, U1, U2]), - Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, - process_taddrs(T, [Entry | Acc]); -%% Bad v1 or v2 -process_taddrs([{TDomain, TAddr} | T], Acc) -> - ?vtrace("process_taddrs -> entry when bad v1/v2 with" - "~n TDomain: ~p" - "~n TAddr: ~p", [TDomain, TAddr]), - user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), - process_taddrs(T, Acc); + "~n TDomain: ~p" + "~n TAddress: ~p", [TDomain, TAddress]), + case transform_taddr(TDomain, TAddress) of + {ok, DestAddr} -> + ?vtrace("process_taddrs -> transformed: " + "~n DestAddr: ~p", [DestAddr]), + Entry = DestAddr, + process_taddrs(T, [Entry | Acc]); + {error, Reason} -> + ?vinfo("Failed transforming v1/v2 domain and address: " + "~n Reason: ~p", [Reason]), + user_err("Bad TDomain/TAddress: ~w/~w", [TDomain, TAddress]), + process_taddrs(T, Acc) + end; process_taddrs(Crap, Acc) -> - throw({error, {taddrs_crap, Crap, Acc}}). + throw({error, {bad_taddrs, Crap, Acc}}). mk_v1_v2_packet_list(To, Packet, Len, Pdu) -> diff --git a/lib/snmp/src/app/snmp.appup.src b/lib/snmp/src/app/snmp.appup.src index 5deb40be0f..8e1855b4df 100644 --- a/lib/snmp/src/app/snmp.appup.src +++ b/lib/snmp/src/app/snmp.appup.src @@ -22,82 +22,82 @@ %% ----- U p g r a d e ------------------------------------------------------- [ + {"4.20.1", + [ + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, + {load_module, snmp_conf, soft_purge, soft_purge, []}, + {load_module, snmp_config, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_config]}, + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mpd]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]} + ] + }, + {"4.20", + [ + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, + {load_module, snmp_target_mib, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, + {load_module, snmp_conf, soft_purge, soft_purge, []}, + {load_module, snmp_config, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_config]}, + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mpd]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]} + ] + }, {"4.19", [ {load_module, snmpa, soft_purge, soft_purge, []}, - {load_module, snmpm, soft_purge, soft_purge, [snmpm_server]}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, {load_module, snmpa_usm, soft_purge, soft_purge, []}, {load_module, snmpm_usm, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, {load_module, snmp_pdus, soft_purge, soft_purge, []}, {load_module, snmp_conf, soft_purge, soft_purge, []}, - {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpa_conf, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, {load_module, snmp_misc, soft_purge, soft_purge, []}, {load_module, snmp_config, soft_purge, soft_purge, []}, - {load_module, snmpa_mpd, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, {load_module, snmpa_trap, soft_purge, soft_purge, [snmpa_mpd, snmp_notification_mib, snmp_target_mib, snmpa_net_if]}, {load_module, snmpa_acm, soft_purge, soft_purge, [snmp_conf, snmpa_mpd, snmp_target_mib]}, {load_module, snmpa_conf, soft_purge, soft_purge, - [snmp_notification_mib]}, + [snmp_config, snmp_notification_mib]}, + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, {load_module, snmp_notification_mib, soft_purge, soft_purge, [snmp_conf, snmp_target_mib]}, {load_module, snmp_community_mib, soft_purge, soft_purge, []}, {load_module, snmp_target_mib, soft_purge, soft_purge, [snmp_conf]}, - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_net_if]}, - {update, snmpa_net_if, soft, soft_purge, soft_purge, - [snmp_conf, snmpa_mpd]}, - {update, snmpa_agent, soft, soft_purge, soft_purge, - [snmpa_acm, snmpa_mpd, snmpa_trap]} - ] - }, - {"4.18", - [ - {load_module, snmpa, soft_purge, soft_purge, []}, - {load_module, snmpm, soft_purge, soft_purge, [snmpm_server]}, - {load_module, snmpa_usm, soft_purge, soft_purge, []}, - {load_module, snmpm_usm, soft_purge, soft_purge, []}, - {load_module, snmp_misc, soft_purge, soft_purge, []}, - {load_module, snmp_log, soft_purge, soft_purge, []}, - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmp_conf, soft_purge, soft_purge, []}, - {load_module, snmp_config, soft_purge, soft_purge, [snmp_conf]}, - {load_module, snmpa_conf, soft_purge, soft_purge, []}, - {load_module, snmpa_mpd, soft_purge, soft_purge, [snmp_conf]}, - {load_module, snmpa_vacm, soft_purge, soft_purge, []}, - {load_module, snmpa_trap, soft_purge, soft_purge, - [snmpa_mpd, snmp_notification_mib, snmp_target_mib, snmpa_net_if]}, - {load_module, snmpa_acm, soft_purge, soft_purge, - [snmp_conf, snmpa_mpd, snmp_target_mib]}, - {load_module, snmpa, soft_purge, soft_purge, - [snmp_community_mib, - snmp_framework_mib, - snmp_standard_mib, - snmp_target_mib, - snmp_user_based_sm_mib, - snmp_view_based_acm_mib]}, - {load_module, snmp_notification_mib, soft_purge, soft_purge, - [snmp_conf, snmp_target_mib, snmpa_mib_lib]}, - {load_module, snmp_community_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_framework_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_standard_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_target_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_user_based_sm_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, - [snmpa_mib_lib, snmpa_vacm]}, - {load_module, snmpa_mib_lib, soft_purge, soft_purge, []}, - - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_net_if]}, - + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, {update, snmpa_net_if, soft, soft_purge, soft_purge, [snmp_conf, snmpa_mpd]}, {update, snmpa_agent, soft, soft_purge, soft_purge, @@ -109,84 +109,82 @@ %% ------D o w n g r a d e --------------------------------------------------- [ + {"4.20.1", + [ + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, + {load_module, snmp_conf, soft_purge, soft_purge, []}, + {load_module, snmp_config, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_config]}, + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mpd]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]} + ] + }, + {"4.20", + [ + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, + {load_module, snmp_target_mib, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, + {load_module, snmp_conf, soft_purge, soft_purge, []}, + {load_module, snmp_config, soft_purge, soft_purge, []}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_config]}, + {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mpd]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]} + ] + }, {"4.19", [ {load_module, snmpa, soft_purge, soft_purge, []}, - {load_module, snmpm, soft_purge, soft_purge, [snmpm_server]}, + {load_module, snmpm, soft_purge, soft_purge, + [snmpm_server, snmpm_config, snmp_config]}, {load_module, snmpa_usm, soft_purge, soft_purge, []}, {load_module, snmpm_usm, soft_purge, soft_purge, []}, {load_module, snmp_log, soft_purge, soft_purge, []}, {load_module, snmp_pdus, soft_purge, soft_purge, []}, {load_module, snmp_conf, soft_purge, soft_purge, []}, - {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpa_conf, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, {load_module, snmp_misc, soft_purge, soft_purge, []}, {load_module, snmp_config, soft_purge, soft_purge, []}, - {load_module, snmpa_mpd, soft_purge, soft_purge, [snmp_conf]}, + {load_module, snmpa_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config]}, + {load_module, snmpm_mpd, soft_purge, soft_purge, + [snmp_conf, snmp_config, snmpm_config]}, {load_module, snmpa_trap, soft_purge, soft_purge, [snmpa_mpd, snmp_notification_mib, snmp_target_mib, snmpa_net_if]}, {load_module, snmpa_acm, soft_purge, soft_purge, [snmp_conf, snmpa_mpd, snmp_target_mib]}, {load_module, snmpa_conf, soft_purge, soft_purge, - [snmp_notification_mib]}, + [snmp_config, snmp_notification_mib]}, + {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, []}, {load_module, snmp_notification_mib, soft_purge, soft_purge, [snmp_conf, snmp_target_mib]}, {load_module, snmp_community_mib, soft_purge, soft_purge, []}, {load_module, snmp_target_mib, soft_purge, soft_purge, [snmp_conf]}, - - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_net_if]}, - - {update, snmpa_net_if, soft, soft_purge, soft_purge, - [snmp_conf, snmpa_mpd]}, - {update, snmpa_agent, soft, soft_purge, soft_purge, - [snmpa_acm, snmpa_mpd, snmpa_trap]} - ] - }, - {"4.18", - [ - {load_module, snmpa, soft_purge, soft_purge, []}, - {load_module, snmpm, soft_purge, soft_purge, [snmpm_server]}, - {load_module, snmpa_usm, soft_purge, soft_purge, []}, - {load_module, snmpm_usm, soft_purge, soft_purge, []}, - {load_module, snmp_misc, soft_purge, soft_purge, []}, - {load_module, snmp_log, soft_purge, soft_purge, []}, - {load_module, snmp_pdus, soft_purge, soft_purge, []}, - {load_module, snmp_conf, soft_purge, soft_purge, []}, - {load_module, snmpa_conf, soft_purge, soft_purge, [snmp_conf]}, - {load_module, snmp_config, soft_purge, soft_purge, []}, - {load_module, snmpa_mpd, soft_purge, soft_purge, [snmp_conf]}, - {load_module, snmpa_vacm, soft_purge, soft_purge, []}, - {load_module, snmpa_trap, soft_purge, soft_purge, - [snmpa_mpd, snmp_notification_mib, snmp_target_mib, snmpa_net_if]}, - {load_module, snmpa_acm, soft_purge, soft_purge, - [snmp_conf, snmpa_mpd, snmp_target_mib]}, - {load_module, snmpa, soft_purge, soft_purge, - [snmp_community_mib, - snmp_framework_mib, - snmp_standard_mib, - snmp_target_mib, - snmp_user_based_sm_mib, - snmp_view_based_acm_mib]}, - {load_module, snmp_notification_mib, soft_purge, soft_purge, - [snmp_conf, snmp_target_mib, snmpa_mib_lib]}, - {load_module, snmp_community_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_framework_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_standard_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_target_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_user_based_sm_mib, soft_purge, soft_purge, - [snmpa_mib_lib]}, - {load_module, snmp_view_based_acm_mib, soft_purge, soft_purge, - [snmpa_mib_lib, snmpa_vacm]}, - {load_module, snmpa_mib_lib, soft_purge, soft_purge, []}, - - {update, snmpm_net_if, soft, soft_purge, soft_purge, []}, - {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_net_if]}, - + {update, snmpm_net_if, soft, soft_purge, soft_purge, + [snmp_conf, snmpm_mpd, snmpm_config]}, + {update, snmpm_config, soft, soft_purge, soft_purge, [snmp_conf]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_net_if, snmpm_mpd, snmpm_config]}, {update, snmpa_net_if, soft, soft_purge, soft_purge, [snmp_conf, snmpa_mpd]}, {update, snmpa_agent, soft, soft_purge, soft_purge, diff --git a/lib/snmp/src/compile/Makefile b/lib/snmp/src/compile/Makefile index 0ceaf276a6..627af6f185 100644 --- a/lib/snmp/src/compile/Makefile +++ b/lib/snmp/src/compile/Makefile @@ -45,11 +45,10 @@ RELSYSDIR = $(RELEASE_PATH)/lib/snmp-$(VSN) include modules.mk -ESCRIPT_BIN = $(ESCRIPT_SRC:%.src=$(BIN)/%) - -ERL_FILES = $(MODULES:%=%.erl) - -TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ESCRIPT_BIN) +ESCRIPT_BIN = $(ESCRIPT_SRC:%.src=$(BIN)/%) +ERL_FILES = $(MODULES:%=%.erl) +EBIN_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +TARGET_FILES = $(EBIN_FILES) $(ESCRIPT_BIN) GENERATED_PARSER = $(PARSER_MODULE:%=%.erl) @@ -125,7 +124,7 @@ release_spec: opt $(INSTALL_DIR) $(RELSYSDIR)/src/compiler $(INSTALL_DATA) $(ESCRIPT_SRC) $(PARSER_SRC) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src/compiler $(INSTALL_DIR) $(RELSYSDIR)/ebin - $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(EBIN_FILES) $(RELSYSDIR)/ebin $(INSTALL_DIR) $(RELSYSDIR)/bin $(INSTALL_SCRIPT) $(ESCRIPT_BIN) $(RELSYSDIR)/bin diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl index 195c238184..5e6b81f1ec 100644 --- a/lib/snmp/src/compile/snmpc.erl +++ b/lib/snmp/src/compile/snmpc.erl @@ -108,6 +108,7 @@ compile(FileName) -> %% {i, [import_dir_string()]} ["./"] %% {il, [import_lib_dir_string()]} [] %% {warnings, bool()} true +%% warnings_as_errors %% {outdir, string()} "./" %% description %% reference @@ -199,6 +200,8 @@ get_options([reference|Opts], Formats, Args) -> get_options(Opts, ["~n reference"|Formats], Args); get_options([{warnings, Val}|Opts], Formats, Args) -> get_options(Opts, ["~n warnings: ~w"|Formats], [Val|Args]); +get_options([warnings_as_errors|Opts], Formats, Args) -> + get_options(Opts, ["~n warnings_as_errors"|Formats], Args); get_options([{verbosity, Val}|Opts], Formats, Args) -> get_options(Opts, ["~n verbosity: ~w"|Formats], [Val|Args]); get_options([imports|Opts], Formats, Args) -> @@ -261,6 +264,8 @@ check_options([{group_check, Atom} | T]) when is_atom(Atom) -> check_options([{warnings, Bool} | T]) -> check_bool(warnings, Bool), check_options(T); +check_options([warnings_as_errors | T]) -> + check_options(T); check_options([{db, volatile} | T]) -> check_options(T); check_options([{db, persistent} | T]) -> @@ -331,6 +336,9 @@ get_agent_capabilities(Options) -> get_module_compliance(Options) -> get_bool_option(module_compliance, Options). +get_warnings_as_errors(Options) -> + lists:member(warnings_as_errors, Options). + get_relaxed_row_name_assign_check(Options) -> lists:member(relaxed_row_name_assign_check, Options). @@ -409,6 +417,7 @@ init(From, MibFileName, Options) -> put(reference, get_reference(Options)), put(agent_capabilities, get_agent_capabilities(Options)), put(module_compliance, get_module_compliance(Options)), + put(warnings_as_errors, get_warnings_as_errors(Options)), File = filename:rootname(MibFileName, ".mib"), put(filename, filename:basename(File ++ ".mib")), R = case catch c_impl(File) of diff --git a/lib/snmp/src/compile/snmpc.src b/lib/snmp/src/compile/snmpc.src index 5f9b154bfa..4e91ae9a03 100644 --- a/lib/snmp/src/compile/snmpc.src +++ b/lib/snmp/src/compile/snmpc.src @@ -50,7 +50,8 @@ %% The default verbosity (silence) will be filled in %% during argument processing. verbosity, - warnings = false + warnings = false, + warnings_as_errors = false }). @@ -74,6 +75,7 @@ %% --version %% --verbosity V %% --warnings +%% --wae main(Args) when is_list(Args) -> case (catch process_args(Args)) of ok -> @@ -156,7 +158,8 @@ mk_mib_options(#state{outdir = OutDir, %% The default verbosity (silence) will be filled in %% during argument processing. verbosity = V, - warnings = W}) -> + warnings = W, + warnings_as_errors = WAE}) -> [{outdir, OutDir}, {db, DB}, {i, IDs}, @@ -178,7 +181,8 @@ mk_mib_options(#state{outdir = OutDir, maybe_option(Imp, imports) ++ maybe_option(MI, module_identity) ++ maybe_option(MC, module_compliance) ++ - maybe_option(AC, agent_capabilities). + maybe_option(AC, agent_capabilities) ++ + maybe_option(WE, warnings_as_errors). maybe_option(true, Opt) -> [Opt]; maybe_option(_, _) -> []. @@ -292,6 +296,8 @@ process_args(["--nd"|Args], State) -> process_args(Args, State#state{no_defaults = true}); process_args(["--rrnac"|Args], State) -> process_args(Args, State#state{relaxed_row_name_assigne_check = true}); +process_args(["--wae"|Args], State) -> + process_args(Args, State#state{warnings_as_errors = true}); process_args([MIB], State) -> Ext = filename:extension(MIB), if @@ -371,6 +377,8 @@ usage() -> "~n a warning. " "~n By default it is not included, but if this option is " "~n present it will be. " + "~n --wae - Warnings as errors. " + "~n Indicates that warnings shall be treated as errors. " "~n " "~n", []), halt(1). diff --git a/lib/snmp/src/compile/snmpc_lib.hrl b/lib/snmp/src/compile/snmpc_lib.hrl index 000486e728..35ec9abd03 100644 --- a/lib/snmp/src/compile/snmpc_lib.hrl +++ b/lib/snmp/src/compile/snmpc_lib.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009. All Rights Reserved. +%% Copyright Ericsson AB 2009-2011. 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 @@ -20,8 +20,17 @@ -ifndef(snmpc_lib). -define(snmpc_lib, true). --define(vwarning(F, A), ?verbosity(warning, F, A, ignore)). --define(vwarning2(F, A, MibLine), ?verbosity(warning, F, A, MibLine)). +-define(vwarning(F, A), + case get(warnings_as_errors) of + true -> snmpc_lib:error(F, A); + _ -> ?verbosity(warning, F, A, ignore) + end). + +-define(vwarning2(F, A, MibLine), + case get(warnings_as_errors) of + true -> snmpc_lib:error(F, A, MibLine); + _ -> ?verbosity(warning, F, A, MibLine) + end). -define(vinfo(F, A), ?verbosity(info, F, A, ignore)). -define(vinfo2(F, A, MibLine), ?verbosity(info, F, A, MibLine)). -define(vlog(F, A), ?verbosity(log, F, A, ignore)). diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl index 0d084332de..6d2ac8d747 100644 --- a/lib/snmp/src/manager/snmpm.erl +++ b/lib/snmp/src/manager/snmpm.erl @@ -50,7 +50,7 @@ register_agent/2, register_agent/3, register_agent/4, unregister_agent/2, unregister_agent/3, which_agents/0, which_agents/1, - agent_info/2, update_agent_info/4, + agent_info/2, update_agent_info/3, update_agent_info/4, register_usm_user/3, unregister_usm_user/2, which_usm_users/0, which_usm_users/1, @@ -167,6 +167,7 @@ -include_lib("snmp/include/snmp_types.hrl"). -include("snmpm_atl.hrl"). -include("snmpm_internal.hrl"). +-include("snmp_verbosity.hrl"). -define(DEFAULT_AGENT_PORT, 161). @@ -447,8 +448,11 @@ agent_info(Addr, Port, Item) -> Error end. +update_agent_info(UserId, TargetName, Info) when is_list(Info) -> + snmpm_config:update_agent_info(UserId, TargetName, Info). + update_agent_info(UserId, TargetName, Item, Val) -> - snmpm_config:update_agent_info(UserId, TargetName, Item, Val). + update_agent_info(UserId, TargetName, [{Item, Val}]). %% Backward compatibility functions update_agent_info(UserId, Addr, Port, Item, Val) -> diff --git a/lib/snmp/src/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl index fd6da3e71a..c2e57abddb 100644 --- a/lib/snmp/src/manager/snmpm_config.erl +++ b/lib/snmp/src/manager/snmpm_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. 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 @@ -36,7 +36,8 @@ user_info/0, user_info/1, user_info/2, register_agent/3, unregister_agent/2, - agent_info/0, agent_info/2, agent_info/3, update_agent_info/4, + agent_info/0, agent_info/2, agent_info/3, + update_agent_info/3, update_agent_info/4, which_agents/0, which_agents/1, is_known_engine_id/2, @@ -84,7 +85,9 @@ backup/1, - mk_target_name/3 + mk_target_name/3, + + default_transport_domain/0 ]). @@ -127,23 +130,24 @@ %% Macros and Constants: --define(SERVER, ?MODULE). --define(BACKUP_DB, snmpm_config_backup). --define(CONFIG_DB, snmpm_config_db). +-define(SERVER, ?MODULE). +-define(BACKUP_DB, snmpm_config_backup). +-define(CONFIG_DB, snmpm_config_db). -define(DEFAULT_USER, default_user). -define(DEFAULT_AGENT_PORT, 161). --define(IRB_DEFAULT, auto). -%% -define(IRB_DEFAULT, {user, timer:seconds(15)}). +-define(IRB_DEFAULT, auto). +%% -define(IRB_DEFAULT, {user, timer:seconds(15)}). --define(USER_MOD_DEFAULT, snmpm_user_default). --define(USER_DATA_DEFAULT, undefined). +-define(USER_MOD_DEFAULT, snmpm_user_default). +-define(USER_DATA_DEFAULT, undefined). %% -define(DEF_ADDR_TAG, default_addr_tag). -define(DEFAULT_TARGETNAME, default_agent). --define(DEF_PORT_TAG, default_port_tag). +-define(DEF_PORT_TAG, default_port_tag). +-define(SUPPORTED_DOMAINS, [transportDomainUdpIpv4, transportDomainUdpIpv6]). -ifdef(snmp_debug). -define(GS_START_LINK(Opts), @@ -159,6 +163,11 @@ %%%------------------------------------------------------------------- %%% API %%%------------------------------------------------------------------- + +default_transport_domain() -> + transportDomainUdpIpv4. + + start_link(Opts) -> ?d("start_link -> entry with" "~n Opts: ~p", [Opts]), @@ -269,9 +278,10 @@ do_user_info(_UserId, BadItem) -> error({not_found, BadItem}). -%% A target-name constructed in this way is a string with the following +%% A target-name constructed in this way is a string with the following: %% <IP-address>:<Port>-<Version> -%% +%% This is intended for backward compatibility and therefor has +%% only support for IPv4 addresses and *no* other transport domain. mk_target_name(Addr0, Port, Config) when is_list(Config) -> Version = case lists:keysearch(version, 1, Config) of @@ -280,7 +290,6 @@ mk_target_name(Addr0, Port, Config) when is_list(Config) -> false -> select_lowest_supported_version() end, -%% p("mk_target_name -> Version: ~p", [Version]), case normalize_address(Addr0) of {A, B, C, D} -> lists:flatten( @@ -308,57 +317,99 @@ select_lowest_supported_version([H|T], Versions) -> end. -register_agent(UserId, _TargetName, _Config) when (UserId =:= user_id) -> +register_agent(UserId, _TargetName, _Config0) when (UserId =:= user_id) -> {error, {bad_user_id, UserId}}; -register_agent(UserId, TargetName, Config) +register_agent(UserId, TargetName, Config0) when (is_list(TargetName) andalso (length(TargetName) > 0) andalso - is_list(Config)) -> + is_list(Config0)) -> -%% p("register_agent -> entry with" -%% "~n UserId: ~p" -%% "~n TargetName: ~p" -%% "~n Config: ~p", [UserId, TargetName, Config]), + ?vtrace("register_agent -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Config0: ~p", [UserId, TargetName, Config0]), %% Check: %% 1) That the mandatory configs are present - %% 2) That the illegal config user_id (used internally) is - %% not present + %% 2) That no illegal config, e.g. user_id (used internally), + %% is not present %% 3) Check that there are no invalid or erroneous configs - %% 4) Chack that the manager is capable to use the selected version - case verify_agent_config(Config) of - ok -> + %% 4) Check that the manager is capable of using the selected version + case verify_agent_config(Config0) of + {ok, Config} -> call({register_agent, UserId, TargetName, Config}); Error -> Error end. -verify_agent_config(Conf) -> - ok = verify_mandatory(Conf, [engine_id, address, reg_type]), - case verify_invalid(Conf, [user_id]) of - ok -> - case verify_agent_config2(Conf) of - ok -> - {ok, Vsns} = system_info(versions), - Vsn = - case lists:keysearch(version, 1, Conf) of - {value, {version, V}} -> - V; - false -> - v1 - end, - case lists:member(Vsn, Vsns) of - true -> - ok; - false -> - {error, {version_not_supported_by_manager, Vsn, Vsns}} - end - end; - Error -> +verify_agent_config(Conf0) -> + try + begin + verify_mandatory(Conf0, [engine_id, address, reg_type]), + verify_invalid(Conf0, [user_id]), + Conf = verify_agent_config3(Conf0), + Vsns = versions(), + Vsn = which_version(Conf), + verify_version(Vsn, Vsns), + {ok, Conf} + end + catch + throw:Error -> Error end. +versions() -> + case system_info(versions) of + {ok, Vsns} -> + Vsns; + {error, _} = ERROR -> + throw(ERROR) + end. + +which_version(Conf) -> + case lists:keysearch(version, 1, Conf) of + {value, {version, V}} -> + V; + false -> + v1 + end. + +verify_version(Vsn, Vsns) -> + case lists:member(Vsn, Vsns) of + true -> + ok; + false -> + Reason = {version_not_supported_by_manager, Vsn, Vsns}, + throw({error, Reason}) + end. + +verify_agent_config3(Conf0) -> + %% Fix (transport) address and domain + {TDomain, Conf1} = + case lists:keysearch(tdomain, 1, Conf0) of + {value, {tdomain, Dom}} -> + {Dom, Conf0}; + false -> + Dom = default_transport_domain(), + {Dom, [{tdomain, Dom} | Conf0]} + end, + Conf2 = case lists:keysearch(address, 1, Conf1) of + {value, {address, Address}} -> + lists:keyreplace(address, 1, Conf1, + {address, {TDomain, Address}}); + false -> + %% This is a mandatory config option, + %% a later test will detect this + Conf1 + end, + case verify_agent2(Conf2) of + {ok, Conf} -> + Conf; + {error, _} = ERROR -> + throw(ERROR) + end. + verify_agent_config2(Conf) -> verify_agent2(Conf). @@ -366,6 +417,7 @@ verify_agent_config2(Conf) -> unregister_agent(UserId, TargetName) -> call({unregister_agent, UserId, TargetName}). +%% This is the old style agent unregistration (using Addr and Port). unregister_agent(UserId, Addr0, Port) -> Addr = normalize_address(Addr0), case do_agent_info(Addr, Port, target_name) of @@ -421,17 +473,51 @@ which_agents(UserId) -> Agents = ets:match(snmpm_agent_table, Pat), [TargetName || [TargetName] <- Agents]. - -update_agent_info(UserId, TargetName, Item, Val0) - when (Item =/= user_id) -> - case (catch verify_val(Item, Val0)) of - {ok, Val} -> - call({update_agent_info, UserId, TargetName, Item, Val}); - Error -> + +verify_agent_info(TargetName, Info0) -> + try + begin + verify_invalid(Info0, [user_id]), + %% Check if address is part of the list and + %% if so update it with the domain info. + Info = + case lists:keysearch(address, 1, Info0) of + {value, {address, Addr}} -> + %% If domain is part of the info, then use it. + %% If not, lookup what is already stored for + %% this agent and use that. + Domain = + case lists:keysearch(tdomain, 1, Info0) of + {value, {tdomain, Dom}} -> + Dom; + false -> + {ok, Dom} = + agent_info(TargetName, tdomain), + Dom + end, + Addr2 = {Domain, Addr}, + lists:keyreplace(address, 1, Info0, {address, Addr2}); + false -> + Info0 + end, + verify_agent2(Info) + end + catch + throw:Error -> Error end. -%% Backward compatibillity +update_agent_info(UserId, TargetName, Info) -> + call({update_agent_info, UserId, TargetName, Info}). + +%% <BACKWARD-COMPAT-2> +%% This is wrapped in the interface module, so this function is +%% only here to catch code-upgrade problems. +update_agent_info(UserId, TargetName, Item, Val) -> + update_agent_info(UserId, TargetName, [{Item, Val}]). +%% </BACKWARD-COMPAT-2> + +%% <BACKWARD-COMPAT-1> update_agent_info(UserId, Addr, Port, Item, Val) -> case agent_info(Addr, Port, target_name) of {ok, TargetName} -> @@ -439,6 +525,7 @@ update_agent_info(UserId, Addr, Port, Item, Val) -> Error -> Error end. +%% </BACKWARD-COMPAT-1> is_known_engine_id(EngineID, TargetName) -> case agent_info(TargetName, engine_id) of @@ -650,22 +737,14 @@ unregister_usm_user(EngineID, Name) call({unregister_usm_user, EngineID, Name}). verify_usm_user_config(EngineID, Name, Config) -> - %% case verify_mandatory(Config, []) of - %% ok -> - %% case verify_invalid(Config, [engine_id, name]) of - %% ok -> - %% verify_usm_user_config2(EngineID, Name, Config); - %% Error -> - %% Error - %% end; - %% Error -> - %% Error - %% end. - ok = verify_mandatory(Config, []), - case verify_invalid(Config, [engine_id, name]) of - ok -> - verify_usm_user_config2(EngineID, Name, Config); - Error -> + try + begin + verify_mandatory(Config, []), + verify_invalid(Config, [engine_id, name]), + verify_usm_user_config2(EngineID, Name, Config) + end + catch + throw:Error -> Error end. @@ -1590,6 +1669,7 @@ check_agent_config2(Agent) -> throw(Err) end. +%% For backward compatibility check_agent_config({UserId, TargetName, Community, @@ -1597,10 +1677,27 @@ check_agent_config({UserId, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}) -> + TDomain = default_transport_domain(), + check_agent_config({UserId, + TargetName, + Community, + TDomain, Ip, Port, + EngineId, + Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel}); + +check_agent_config({UserId, + TargetName, + Community, + TDomain, Ip, Port, + EngineId, + Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel}) -> ?vtrace("check_agent_config -> entry with" "~n UserId: ~p" "~n TargetName: ~p" "~n Community: ~p" + "~n TDomain: ~p" "~n Ip: ~p" "~n Port: ~p" "~n EngineId: ~p" @@ -1610,15 +1707,16 @@ check_agent_config({UserId, "~n SecModel: ~p" "~n SecName: ~p" "~n SecLevel: ~p", - [UserId, TargetName, Community, Ip, Port, + [UserId, TargetName, Community, + TDomain, Ip, Port, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel]), - Addr = normalize_address(Ip), + Addr = normalize_address(TDomain, Ip), ?vtrace("check_agent_config -> Addr: ~p", [Addr]), Agent = {UserId, TargetName, Community, - Addr, Port, + TDomain, Addr, Port, EngineId, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel}, @@ -1644,6 +1742,7 @@ init_agent_config({UserId, TargetName, Config}) -> end. +%% For backward compatibility verify_agent({UserId, TargetName, Comm, @@ -1651,48 +1750,68 @@ verify_agent({UserId, EngineId, Timeout, MMS, Version, SecModel, SecName, SecLevel}) -> - ?vtrace("verify_agent -> entry with" + TDomain = default_transport_domain(), + verify_agent({UserId, + TargetName, + Comm, + TDomain, Ip, Port, + EngineId, + Timeout, MMS, + Version, SecModel, SecName, SecLevel}); + +verify_agent({UserId, + TargetName, + Comm, + TDomain, Ip, Port, + EngineId, + Timeout, MMS, + Version, SecModel, SecName, SecLevel}) -> + ?vdebug("verify_agent -> entry with" "~n UserId: ~p" "~n TargetName: ~p", [UserId, TargetName]), snmp_conf:check_string(TargetName, {gt, 0}), - case verify_val(address, Ip) of - {ok, Addr} -> - snmp_conf:check_integer(Port, {gt, 0}), - Conf = - [{reg_type, target_name}, - {address, Addr}, - {port, Port}, - {community, Comm}, - {engine_id, EngineId}, - {timeout, Timeout}, - {max_message_size, MMS}, - {version, Version}, - {sec_model, SecModel}, - {sec_name, SecName}, - {sec_level, SecLevel} - ], - case verify_agent2(Conf) of - ok -> - {UserId, TargetName, Conf, Version}; - Err -> - throw(Err) - end; - - Error -> - ?vlog("verify_agent -> failed: ~n ~p", [Error]), - throw(Error) + snmp_conf:check_integer(Port, {gt, 0}), + %% Note that the order of Conf *is* important. + %% Some properties may depend on others, so that + %% in order to verify one property, another must + %% be already verified (and present). An example + %% of this is the property 'address', for which + %% the property tdomain is needed. + Conf0 = + [{reg_type, target_name}, + {tdomain, TDomain}, + %% This should be taddress, but what the*... + {address, {TDomain, Ip}}, + {port, Port}, + {community, Comm}, + {engine_id, EngineId}, + {timeout, Timeout}, + {max_message_size, MMS}, + {version, Version}, + {sec_model, SecModel}, + {sec_name, SecName}, + {sec_level, SecLevel} + ], + case verify_agent2(Conf0) of + {ok, Conf} -> + {UserId, TargetName, Conf, Version}; + Err -> + throw(Err) end. -verify_agent2([]) -> - ok; -verify_agent2([{Item, Val}|Items]) -> - case verify_val(Item, Val) of - {ok, _Val} -> - verify_agent2(Items); +verify_agent2(Conf) -> + verify_agent2(Conf, []). + +verify_agent2([], VerifiedConf) -> + {ok, VerifiedConf}; +verify_agent2([{Item, Val0}|Items], VerifiedConf) -> + case verify_val(Item, Val0) of + {ok, Val} -> + verify_agent2(Items, [{Item, Val} | VerifiedConf]); Err -> Err end; -verify_agent2([Bad|_]) -> +verify_agent2([Bad|_], _VerifiedConf) -> {error, {bad_agent_config, Bad}}. @@ -1708,14 +1827,28 @@ read_users_config_file(Dir) -> check_user_config({Id, Mod, Data}) -> + ?vtrace("check_user_config -> entry with" + "~n Id: ~p" + "~n Mod: ~p" + "~n Data: ~p", [Id, Mod, Data]), check_user_config({Id, Mod, Data, []}); -check_user_config({Id, Mod, _Data, DefaultAgentConfig} = User) +check_user_config({Id, Mod, Data, DefaultAgentConfig} = _User) when (Id =/= ?DEFAULT_USER) andalso is_list(DefaultAgentConfig) -> + ?vtrace("check_user_config -> entry with" + "~n Id: ~p" + "~n Mod: ~p" + "~n Data: ~p" + "~n DefaultAgentConfig: ~p", + [Id, Mod, Data, DefaultAgentConfig]), case (catch verify_user_behaviour(Mod)) of ok -> + ?vtrace("check_user_config -> user behaviour verified", []), case verify_user_agent_config(DefaultAgentConfig) of - ok -> - {ok, User}; + {ok, DefAgentConf} -> + ?vtrace("check_user_config -> " + "user agent (default) config verified", []), + User2 = {Id, Mod, Data, DefAgentConf}, + {ok, User2}; {error, Reason} -> error({bad_default_agent_config, Reason}) end; @@ -1756,16 +1889,16 @@ verify_user({Id, UserMod, UserData}) -> verify_user({Id, UserMod, UserData, DefaultAgentConfig}) when (Id =/= ?DEFAULT_USER) andalso is_list(DefaultAgentConfig) -> ?d("verify_user -> entry with" - "~n Id: ~p" - "~n UserMod: ~p" - "~n UserData: ~p" + "~n Id: ~p" + "~n UserMod: ~p" + "~n UserData: ~p" "~n DefaultAgentConfig: ~p", [Id, UserMod, UserData, DefaultAgentConfig]), case (catch verify_user_behaviour(UserMod)) of ok -> case verify_user_agent_config(DefaultAgentConfig) of - ok -> - Config = default_agent_config(DefaultAgentConfig), + {ok, DefAgentConf} -> + Config = default_agent_config(DefAgentConf), {ok, #user{id = Id, mod = UserMod, data = UserData, @@ -1783,10 +1916,15 @@ verify_user({Id, _, _, _}) -> {error, {bad_user_id, Id}}. verify_user_agent_config(Conf) -> - case verify_invalid(Conf, [user_id, engine_id, address]) of - ok -> - verify_agent_config2(Conf); - Error -> + try + begin + verify_invalid(Conf, [user_id, engine_id, address]), + verify_agent_config2(Conf) + end + catch + throw:Error -> + ?vdebug("verify_user_agent_config -> throw" + "~n Error: ~p", [Error]), Error end. @@ -2147,6 +2285,16 @@ handle_call({unregister_agent, UserId, TargetName}, _From, State) -> Reply = handle_unregister_agent(UserId, TargetName), {reply, Reply, State}; +handle_call({update_agent_info, UserId, TargetName, Info}, + _From, State) -> + ?vlog("received update_agent_info request: " + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Info: ~p", [UserId, TargetName, Info]), + Reply = handle_update_agent_info(UserId, TargetName, Info), + {reply, Reply, State}; + +%% <BACKWARD-COMPAT> handle_call({update_agent_info, UserId, TargetName, Item, Val}, _From, State) -> ?vlog("received update_agent_info request: " @@ -2156,6 +2304,7 @@ handle_call({update_agent_info, UserId, TargetName, Item, Val}, "~n Val: ~p", [UserId, TargetName, Item, Val]), Reply = handle_update_agent_info(UserId, TargetName, Item, Val), {reply, Reply, State}; +%% </BACKWARD-COMPAT> handle_call({register_usm_user, User}, _From, State) -> ?vlog("received register_usm_user request: " @@ -2517,16 +2666,27 @@ handle_register_agent(UserId, TargetName, Config) -> "~n Config: ~p", [UserId, TargetName, Config]), case (catch agent_info(TargetName, user_id)) of {error, _} -> + ?vtrace("handle_register_agent -> user_id not found in config", []), case ets:lookup(snmpm_user_table, UserId) of [#user{default_agent_config = DefConfig}] -> + ?vtrace("handle_register_agent -> " + "~n DefConfig: ~p", [DefConfig]), + %% First, insert this users default config + ?vtrace("handle_register_agent -> store default config", []), do_handle_register_agent(TargetName, DefConfig), + %% Second, insert the config for this agent + ?vtrace("handle_register_agent -> store config", []), do_handle_register_agent(TargetName, [{user_id, UserId}|Config]), %% <DIRTY-BACKWARD-COMPATIBILLITY> %% And now for some (backward compatibillity) %% dirty crossref stuff + ?vtrace("handle_register_agent -> lookup address", []), {ok, Addr} = agent_info(TargetName, address), + ?vtrace("handle_register_agent -> Addr: ~p, lookup Port", + [Addr]), {ok, Port} = agent_info(TargetName, port), + ?vtrace("handle_register_agent -> register cross-ref fix", []), ets:insert(snmpm_agent_table, {{Addr, Port, target_name}, TargetName}), %% </DIRTY-BACKWARD-COMPATIBILLITY> @@ -2551,10 +2711,18 @@ handle_register_agent(UserId, TargetName, Config) -> do_handle_register_agent(_TargetName, []) -> ok; do_handle_register_agent(TargetName, [{Item, Val}|Rest]) -> + ?vtrace("handle_register_agent -> entry with" + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val: ~p" + "~n Rest: ~p", [TargetName, Item, Val, Rest]), case (catch do_update_agent_info(TargetName, Item, Val)) of ok -> do_handle_register_agent(TargetName, Rest); {error, Reason} -> + ?vtrace("handle_register_agent -> failed updating ~p" + "~n Item: ~p" + "~n Reason: ~p", [Item, Reason]), ets:match_delete(snmpm_agent_table, {TargetName, '_'}), {error, Reason} end; @@ -2589,41 +2757,61 @@ handle_unregister_agent(UserId, TargetName) -> end. -handle_update_agent_info(UserId, TargetName, Item, Val) -> +handle_update_agent_info(UserId, TargetName, Info) -> ?vdebug("handle_update_agent_info -> entry with" "~n UserId: ~p" "~n TargetName: ~p" - "~n Item: ~p" - "~n Val: ~p", [UserId, TargetName, Item, Val]), + "~n Info: ~p", [UserId, TargetName, Info]), + %% Verify ownership case (catch agent_info(TargetName, user_id)) of - {ok, UserId} -> - do_update_agent_info(TargetName, Item, Val); + {ok, UserId} -> + handle_update_agent_info(TargetName, Info); {ok, OtherUserId} -> {error, {not_owner, OtherUserId}}; Error -> Error end. -do_update_agent_info(TargetName, Item, Val0) -> -%% p("do_update_agent_info -> entry with" -%% "~n TargetName: ~p" -%% "~n Item: ~p" -%% "~n Val0: ~p", [TargetName, Item, Val0]), - case verify_val(Item, Val0) of - {ok, Val} -> -%% p("do_update_agent_info -> verified value" -%% "~n Val: ~p", [Val]), - ets:insert(snmpm_agent_table, {{TargetName, Item}, Val}), - ok; +handle_update_agent_info(TargetName, Info0) -> + ?vtrace("handle_update_agent_info -> entry with" + "~n TargetName: ~p" + "~n Info0: ~p", [TargetName, Info0]), + %% Verify info + try verify_agent_info(TargetName, Info0) of + {ok, Info} -> + do_update_agent_info(TargetName, Info); Error -> - ?vlog("do_update_agent_info -> verify value failed: " - "~n TargetName: ~p" - "~n Item: ~p" - "~n Val0: ~p" - "~n Error: ~p", [TargetName, Item, Val0, Error]), - {error, {bad_agent_val, TargetName, Item, Val0}} + Error + catch + throw:Error -> + Error; + T:E -> + {error, {failed_info_verification, Info0, T, E}} end. +handle_update_agent_info(UserId, TargetName, Item, Val) -> + ?vdebug("handle_update_agent_info -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val: ~p", [UserId, TargetName, Item, Val]), + handle_update_agent_info(TargetName, [{Item, Val}]). + +do_update_agent_info(TargetName, Info) -> + InsertItem = + fun({Item, Val}) -> + ets:insert(snmpm_agent_table, {{TargetName, Item}, Val}) + end, + lists:foreach(InsertItem, Info). + +do_update_agent_info(TargetName, Item, Val) -> + ?vtrace("do_update_agent_info -> entry with" + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val: ~p", [TargetName, Item, Val]), + ets:insert(snmpm_agent_table, {{TargetName, Item}, Val}), + ok. + handle_register_usm_user(#usm_user{engine_id = EngineID, name = Name} = User) -> @@ -2791,7 +2979,7 @@ verify_mandatory(Conf, [Mand|Mands]) -> true -> verify_mandatory(Conf, Mands); false -> - {error, {missing_mandatory_config, Mand}} + throw({error, {missing_mandatory_config, Mand}}) end. verify_invalid(_, []) -> @@ -2801,7 +2989,7 @@ verify_invalid(Conf, [Inv|Invs]) -> false -> verify_invalid(Conf, Invs); true -> - {error, {illegal_config, Inv}} + throw({error, {illegal_config, Inv}}) end. @@ -2810,10 +2998,26 @@ verify_val(user_id, UserId) -> verify_val(reg_type, RegType) when (RegType =:= addr_port) orelse (RegType =:= target_name) -> {ok, RegType}; -verify_val(address, Addr0) -> - case normalize_address(Addr0) of +verify_val(tdomain = Item, snmpUDPDomain = _Domain) -> + verify_val(Item, transportDomainUdpIpv4); +verify_val(tdomain, Domain) -> + case lists:member(Domain, ?SUPPORTED_DOMAINS) of + true -> + {ok, Domain}; + false -> + case lists:member(Domain, snmp_conf:all_domains()) of + true -> + error({unsupported_domain, Domain}); + false -> + error({unknown_domain, Domain}) + end + end; +verify_val(address, {Domain, Addr0}) -> + case normalize_address(Domain, Addr0) of {_A1, _A2, _A3, _A4} = Addr -> {ok, Addr}; + {_A1, _A2, _A3, _A4, _A5, _A6, _A7, _A8} = Addr -> + {ok, Addr}; _ when is_list(Addr0) -> case (catch snmp_conf:check_ip(Addr0)) of ok -> @@ -2824,6 +3028,8 @@ verify_val(address, Addr0) -> _ -> error({bad_address, Addr0}) end; +verify_val(address, BadAddress) -> + error({bad_address, BadAddress}); verify_val(port, Port) -> case (catch snmp_conf:check_integer(Port, {gt, 0})) of ok -> @@ -2875,7 +3081,7 @@ verify_val(sec_name, BadName) -> verify_val(sec_level, Level) -> (catch snmp_conf:check_sec_level(Level)); verify_val(Item, _) -> - {error, {no_such_item, Item}}. + {error, {unknown_item, Item}}. %%%------------------------------------------------------------------- @@ -3034,11 +3240,17 @@ init_mini_mib_elems(MibName, [_|T], Res) -> %%---------------------------------------------------------------------- normalize_address(Addr) -> - case inet:getaddr(Addr, inet) of + normalize_address(snmpUDPDomain, Addr). + +normalize_address(snmpUDPDomain, Addr) -> + normalize_address(transportDomainUdpIpv4, Addr); + +normalize_address(Domain, Addr) -> + case inet:getaddr(Addr, td2fam(Domain)) of {ok, Addr2} -> Addr2; _ when is_list(Addr) -> - case (catch snmp_conf:check_ip(Addr)) of + case (catch snmp_conf:check_ip(Domain, Addr)) of ok -> list_to_tuple(Addr); _ -> @@ -3048,6 +3260,9 @@ normalize_address(Addr) -> Addr end. +td2fam(transportDomainUdpIpv4) -> inet; +td2fam(transportDomainUdpIpv6) -> inet6. + %%---------------------------------------------------------------------- diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl index 7712370d28..627838e3d4 100644 --- a/lib/snmp/src/manager/snmpm_mpd.erl +++ b/lib/snmp/src/manager/snmpm_mpd.erl @@ -92,7 +92,7 @@ reset(#state{v3 = V3}) -> %% Purpose: This is the main Message Dispatching function. (see %% section 4.2.1 in rfc2272) %%----------------------------------------------------------------- -process_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) -> +process_msg(Msg, Domain, Addr, Port, State, NoteStore, Logger) -> inc(snmpInPkts), @@ -102,18 +102,18 @@ process_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) -> #message{version = 'version-1', vsn_hdr = Community, data = Data} when State#state.v1 =:= true -> HS = ?empty_msg_size + length(Community), - process_v1_v2c_msg('version-1', NoteStore, Msg, TDomain, - Addr, Port, + process_v1_v2c_msg('version-1', NoteStore, Msg, + Domain, Addr, Port, Community, Data, HS, Logger); %% Version 2 #message{version = 'version-2', vsn_hdr = Community, data = Data} when State#state.v2c =:= true -> HS = ?empty_msg_size + length(Community), - process_v1_v2c_msg('version-2', NoteStore, Msg, TDomain, - Addr, Port, - Community, Data, HS, Logger); - + (catch process_v1_v2c_msg('version-2', NoteStore, Msg, + Domain, Addr, Port, + Community, Data, HS, Logger)); + %% Version 3 #message{version = 'version-3', vsn_hdr = H, data = Data} when State#state.v3 =:= true -> @@ -148,17 +148,30 @@ process_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) -> %%----------------------------------------------------------------- %% Handles a Community based message (v1 or v2c). %%----------------------------------------------------------------- -process_v1_v2c_msg(Vsn, _NoteStore, Msg, snmpUDPDomain, +process_v1_v2c_msg(Vsn, _NoteStore, Msg, Domain, Addr, Port, Community, Data, HS, Log) -> ?vdebug("process_v1_v2c_msg -> entry with" "~n Vsn: ~p" + "~n Domain: ~p" "~n Addr: ~p" "~n Port: ~p" "~n Community: ~p" - "~n HS: ~p", [Vsn, Addr, Port, Community, HS]), + "~n HS: ~p", [Vsn, Domain, Addr, Port, Community, HS]), + {TDomain, TAddress} = + try + begin + TD = snmp_conf:mk_tdomain(Domain), + TA = snmp_conf:mk_taddress(Domain, Addr, Port), + {TD, TA} + end + catch + throw:{error, TReason} -> + throw({discarded, {badarg, Domain, TReason}}) + end, + Max = get_max_message_size(), AgentMax = get_agent_max_message_size(Addr, Port), PduMS = pdu_ms(Max, AgentMax, HS), @@ -170,14 +183,14 @@ process_v1_v2c_msg(Vsn, _NoteStore, Msg, snmpUDPDomain, ?vtrace("process_v1_v2c_msg -> was a pdu", []), Log(Msg), inc_snmp_in(Pdu), - MsgData = {Community, sec_model(Vsn)}, + MsgData = {Community, sec_model(Vsn), TDomain, TAddress}, {ok, Vsn, Pdu, PduMS, MsgData}; Trap when is_record(Trap, trappdu) -> ?vtrace("process_v1_v2c_msg -> was a trap", []), Log(Msg), inc_snmp_in(Trap), - MsgData = {Community, sec_model(Vsn)}, + MsgData = {Community, sec_model(Vsn), TDomain, TAddress}, {ok, Vsn, Trap, PduMS, MsgData}; {'EXIT', Reason} -> @@ -185,11 +198,7 @@ process_v1_v2c_msg(Vsn, _NoteStore, Msg, snmpUDPDomain, "~n Reason: ~p", [Reason]), inc(snmpInASNParseErrs), {discarded, Reason} - end; -process_v1_v2c_msg(_Vsn, _NoteStore, _Msg, TDomain, - _Addr, _Port, - _Comm, _HS, _Data, _Log) -> - {discarded, {badarg, TDomain}}. + end. pdu_ms(MgrMMS, AgentMMS, HS) when AgentMMS < MgrMMS -> AgentMMS - HS; @@ -482,8 +491,8 @@ generate_msg('version-3', NoteStore, Pdu, generate_v3_msg(NoteStore, Pdu, SecModel, SecName, SecLevel, CtxEngineID, CtxName, TargetName, Log); -generate_msg(Vsn, _NoteStore, Pdu, {Community, _SecModel}, Log) -> - generate_v1_v2c_msg(Vsn, Pdu, Community, Log). +generate_msg(Vsn, _NoteStore, Pdu, {Comm, _SecModel}, Log) -> + generate_v1_v2c_msg(Vsn, Pdu, Comm, Log). generate_v3_msg(NoteStore, Pdu, @@ -627,6 +636,8 @@ generate_response_msg('version-3', Pdu, generate_v3_response_msg(Pdu, MsgID, SecModel, SecName, SecLevel, CtxEngineID, CtxName, SecData, Log); generate_response_msg(Vsn, Pdu, {Comm, _SecModel}, Log) -> + generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log); +generate_response_msg(Vsn, Pdu, {Comm, _SecModel, _TDomain, _TAddress}, Log) -> generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log). diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl index a116c9f26b..4d6bd9aa33 100644 --- a/lib/snmp/src/manager/snmpm_net_if.erl +++ b/lib/snmp/src/manager/snmpm_net_if.erl @@ -28,7 +28,8 @@ start_link/2, stop/1, send_pdu/6, % Backward compatibillity - send_pdu/7, + send_pdu/7, % Backward compatibillity + send_pdu/8, inform_response/4, @@ -101,16 +102,21 @@ stop(Pid) -> send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port) -> send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, ?DEFAULT_EXTRA_INFO). -send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo) +send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo) -> + Domain = snmpm_config:default_transport_domain(), + send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo). + +send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo) when is_record(Pdu, pdu) -> ?d("send_pdu -> entry with" "~n Pid: ~p" "~n Pdu: ~p" "~n Vsn: ~p" "~n MsgData: ~p" + "~n Domain: ~p" "~n Addr: ~p" - "~n Port: ~p", [Pid, Pdu, Vsn, MsgData, Addr, Port]), - cast(Pid, {send_pdu, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo}). + "~n Port: ~p", [Pid, Pdu, Vsn, MsgData, Domain, Addr, Port]), + cast(Pid, {send_pdu, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo}). note_store(Pid, NoteStore) -> call(Pid, {note_store, NoteStore}). @@ -380,15 +386,17 @@ handle_call(Req, From, State) -> %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_cast({send_pdu, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo}, State) -> +handle_cast({send_pdu, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo}, + State) -> ?vlog("received send_pdu message with" "~n Pdu: ~p" "~n Vsn: ~p" "~n MsgData: ~p" + "~n Domain: ~p" "~n Addr: ~p" - "~n Port: ~p", [Pdu, Vsn, MsgData, Addr, Port]), + "~n Port: ~p", [Pdu, Vsn, MsgData, Domain, Addr, Port]), maybe_process_extra_info(ExtraInfo), - maybe_handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, State), + maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, Port, State), {noreply, State}; handle_cast({inform_response, Ref, Addr, Port}, State) -> @@ -545,8 +553,9 @@ handle_recv_msg(Addr, Port, Bytes, mpd_state = MpdState, sock = Sock, log = Log} = State) -> + Domain = snmp_conf:which_domain(Addr), % What the ****... Logger = logger(Log, read, Addr, Port), - case (catch snmpm_mpd:process_msg(Bytes, snmpUDPDomain, Addr, Port, + case (catch snmpm_mpd:process_msg(Bytes, Domain, Addr, Port, MpdState, NoteStore, Logger)) of {ok, Vsn, Pdu, MS, ACM} -> @@ -734,17 +743,17 @@ irgc_stop(Ref) -> (catch erlang:cancel_timer(Ref)). -maybe_handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, +maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, Port, #state{filter = FilterMod} = State) -> case (catch FilterMod:accept_send_pdu(Addr, Port, pdu_type_of(Pdu))) of false -> inc(netIfPduOutDrops), ok; _ -> - handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, State) + handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, Port, State) end. -handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, +handle_send_pdu(Pdu, Vsn, MsgData, _Domain, Addr, Port, #state{server = Pid, note_store = NoteStore, sock = Sock, diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl index 58a58507d6..484954addb 100644 --- a/lib/snmp/src/manager/snmpm_server.erl +++ b/lib/snmp/src/manager/snmpm_server.erl @@ -161,7 +161,8 @@ {id, user_id, reg_type, - target, + target, + domain, addr, port, type, @@ -1175,11 +1176,12 @@ handle_sync_get(Pid, UserId, TargetName, Oids, SendOpts, From, State) -> "~n From: ~p", [Pid, UserId, TargetName, Oids, SendOpts, From]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_sync_get -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), ReqId = send_get_request(Oids, Vsn, MsgData, - Addr, Port, Extra, State), + Domain, Addr, Port, + Extra, State), ?vdebug("handle_sync_get -> ReqId: ~p", [ReqId]), Msg = {sync_timeout, ReqId, From}, Timeout = ?SYNC_GET_TIMEOUT(SendOpts), @@ -1190,6 +1192,7 @@ handle_sync_get(Pid, UserId, TargetName, Oids, SendOpts, From, State) -> user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get, @@ -1227,11 +1230,12 @@ handle_sync_get_next(Pid, UserId, TargetName, Oids, SendOpts, "~n From: ~p", [Pid, UserId, TargetName, Oids, SendOpts, From]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_sync_get_next -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), ReqId = send_get_next_request(Oids, Vsn, MsgData, - Addr, Port, Extra, State), + Domain, Addr, Port, + Extra, State), ?vdebug("handle_sync_get_next -> ReqId: ~p", [ReqId]), Msg = {sync_timeout, ReqId, From}, Timeout = ?SYNC_GET_NEXT_TIMEOUT(SendOpts), @@ -1242,6 +1246,7 @@ handle_sync_get_next(Pid, UserId, TargetName, Oids, SendOpts, user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get_next, @@ -1285,10 +1290,11 @@ handle_sync_get_bulk(Pid, UserId, TargetName, NonRep, MaxRep, Oids, SendOpts, "~n From: ~p", [Pid, UserId, TargetName, NonRep, MaxRep, Oids, SendOpts, From]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_sync_get_bulk -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), - ReqId = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, + ReqId = send_get_bulk_request(Oids, Vsn, MsgData, + Domain, Addr, Port, NonRep, MaxRep, Extra, State), ?vdebug("handle_sync_get_bulk -> ReqId: ~p", [ReqId]), Msg = {sync_timeout, ReqId, From}, @@ -1300,6 +1306,7 @@ handle_sync_get_bulk(Pid, UserId, TargetName, NonRep, MaxRep, Oids, SendOpts, user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get_bulk, @@ -1339,11 +1346,12 @@ handle_sync_set(Pid, UserId, TargetName, VarsAndVals, SendOpts, From, State) -> "~n From: ~p", [Pid, UserId, TargetName, VarsAndVals, From]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_sync_set -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), ReqId = send_set_request(VarsAndVals, Vsn, MsgData, - Addr, Port, Extra, State), + Domain, Addr, Port, + Extra, State), ?vdebug("handle_sync_set -> ReqId: ~p", [ReqId]), Msg = {sync_timeout, ReqId, From}, Timeout = ?SYNC_SET_TIMEOUT(SendOpts), @@ -1354,6 +1362,7 @@ handle_sync_set(Pid, UserId, TargetName, VarsAndVals, SendOpts, From, State) -> user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = set, @@ -1391,10 +1400,11 @@ handle_async_get(Pid, UserId, TargetName, Oids, SendOpts, State) -> "~n SendOpts: ~p", [Pid, UserId, TargetName, Oids, SendOpts]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_async_get -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), - ReqId = send_get_request(Oids, Vsn, MsgData, Addr, Port, + ReqId = send_get_request(Oids, Vsn, MsgData, + Domain, Addr, Port, Extra, State), ?vdebug("handle_async_get -> ReqId: ~p", [ReqId]), Expire = ?ASYNC_GET_TIMEOUT(SendOpts), @@ -1402,6 +1412,7 @@ handle_async_get(Pid, UserId, TargetName, Oids, SendOpts, State) -> user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get, @@ -1439,17 +1450,19 @@ handle_async_get_next(Pid, UserId, TargetName, Oids, SendOpts, State) -> "~n SendOpts: ~p", [Pid, UserId, TargetName, Oids, SendOpts]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_async_get_next -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), ReqId = send_get_next_request(Oids, Vsn, MsgData, - Addr, Port, Extra, State), + Domain, Addr, Port, + Extra, State), ?vdebug("handle_async_get_next -> ReqId: ~p", [ReqId]), Expire = ?ASYNC_GET_NEXT_TIMEOUT(SendOpts), Req = #request{id = ReqId, user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get_next, @@ -1494,10 +1507,11 @@ handle_async_get_bulk(Pid, "~n SendOpts: ~p", [Pid, UserId, TargetName, NonRep, MaxRep, Oids, SendOpts]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_async_get_bulk -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), - ReqId = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, + ReqId = send_get_bulk_request(Oids, Vsn, MsgData, + Domain, Addr, Port, NonRep, MaxRep, Extra, State), ?vdebug("handle_async_get_bulk -> ReqId: ~p", [ReqId]), Expire = ?ASYNC_GET_BULK_TIMEOUT(SendOpts), @@ -1505,6 +1519,7 @@ handle_async_get_bulk(Pid, user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = get_bulk, @@ -1541,17 +1556,19 @@ handle_async_set(Pid, UserId, TargetName, VarsAndVals, SendOpts, State) -> "~n SendOpts: ~p", [Pid, UserId, TargetName, VarsAndVals, SendOpts]), case agent_data(TargetName, SendOpts) of - {ok, RegType, Addr, Port, Vsn, MsgData} -> + {ok, RegType, Domain, Addr, Port, Vsn, MsgData} -> ?vtrace("handle_async_set -> send a ~p message", [Vsn]), Extra = ?GET_EXTRA(SendOpts), ReqId = send_set_request(VarsAndVals, Vsn, MsgData, - Addr, Port, Extra, State), + Domain, Addr, Port, + Extra, State), ?vdebug("handle_async_set -> ReqId: ~p", [ReqId]), Expire = ?ASYNC_SET_TIMEOUT(SendOpts), Req = #request{id = ReqId, user_id = UserId, reg_type = RegType, target = TargetName, + domain = Domain, addr = Addr, port = Port, type = set, @@ -2907,7 +2924,7 @@ do_gc(Key, Now) -> %% %%---------------------------------------------------------------------- -send_get_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, +send_get_request(Oids, Vsn, MsgData, Domain, Addr, Port, ExtraInfo, #state{net_if = NetIf, net_if_mod = Mod, mini_mib = MiniMIB}) -> @@ -2918,34 +2935,39 @@ send_get_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, "~n Pdu: ~p" "~n Vsn: ~p" "~n MsgData: ~p" + "~n Domain: ~p" "~n Addr: ~p" - "~n Port: ~p", [Mod, NetIf, Pdu, Vsn, MsgData, Addr, Port]), - (catch Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo)), + "~n Port: ~p", + [Mod, NetIf, Pdu, Vsn, MsgData, Domain, Addr, Port]), + Res = (catch Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, + Domain, Addr, Port, ExtraInfo)), + ?vtrace("send_get_request -> send result:" + "~n ~p", [Res]), Pdu#pdu.request_id. -send_get_next_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, +send_get_next_request(Oids, Vsn, MsgData, Domain, Addr, Port, ExtraInfo, #state{mini_mib = MiniMIB, net_if = NetIf, net_if_mod = Mod}) -> Pdu = make_pdu(get_next, Oids, MiniMIB), - Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo), Pdu#pdu.request_id. -send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, +send_get_bulk_request(Oids, Vsn, MsgData, Domain, Addr, Port, NonRep, MaxRep, ExtraInfo, #state{mini_mib = MiniMIB, net_if = NetIf, net_if_mod = Mod}) -> Pdu = make_pdu(bulk, {NonRep, MaxRep, Oids}, MiniMIB), - Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo), Pdu#pdu.request_id. -send_set_request(VarsAndVals, Vsn, MsgData, Addr, Port, ExtraInfo, +send_set_request(VarsAndVals, Vsn, MsgData, Domain, Addr, Port, ExtraInfo, #state{mini_mib = MiniMIB, net_if = NetIf, net_if_mod = Mod}) -> Pdu = make_pdu(set, VarsAndVals, MiniMIB), - Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Domain, Addr, Port, ExtraInfo), Pdu#pdu.request_id. %% send_discovery(Vsn, MsgData, Addr, Port, ExtraInfo, @@ -3181,10 +3203,11 @@ agent_data(TargetName, SendOpts) -> {Comm, SecModel} end, + Domain = agent_data_item(tdomain, Info), Addr = agent_data_item(address, Info), Port = agent_data_item(port, Info), RegType = agent_data_item(reg_type, Info), - {ok, RegType, Addr, Port, version(Version), MsgData}; + {ok, RegType, Domain, Addr, Port, version(Version), MsgData}; Error -> Error end. diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl index 20f4455d10..7249def24e 100644 --- a/lib/snmp/src/misc/snmp_conf.erl +++ b/lib/snmp/src/misc/snmp_conf.erl @@ -37,7 +37,9 @@ check_timer/1, + all_domains/0, check_domain/1, + all_tdomains/0, check_tdomain/1, mk_tdomain/1, which_domain/1, @@ -345,6 +347,25 @@ check_sec_level(BadSecLevel) -> %% --------- +all_tdomains() -> + [ + ?transportDomainUdpIpv4, + ?transportDomainUdpIpv6, + ?transportDomainUdpIpv4z, + ?transportDomainUdpIpv6z, + ?transportDomainTcpIpv4, + ?transportDomainTcpIpv6, + ?transportDomainTcpIpv4z, + ?transportDomainTcpIpv6z, + ?transportDomainSctpIpv4, + ?transportDomainSctpIpv6, + ?transportDomainSctpIpv4z, + ?transportDomainSctpIpv6z, + ?transportDomainLocal, + ?transportDomainUdpDns, + ?transportDomainTcpDns, + ?transportDomainSctpDns + ]. check_tdomain(TDomain) -> SupportedTDomains = @@ -353,25 +374,7 @@ check_tdomain(TDomain) -> ?transportDomainUdpIpv4, ?transportDomainUdpIpv6 ], - AllTDomains = - [ - ?transportDomainUdpIpv4, - ?transportDomainUdpIpv6, - ?transportDomainUdpIpv4z, - ?transportDomainUdpIpv6z, - ?transportDomainTcpIpv4, - ?transportDomainTcpIpv6, - ?transportDomainTcpIpv4z, - ?transportDomainTcpIpv6z, - ?transportDomainSctpIpv4, - ?transportDomainSctpIpv6, - ?transportDomainSctpIpv4z, - ?transportDomainSctpIpv6z, - ?transportDomainLocal, - ?transportDomainUdpDns, - ?transportDomainTcpDns, - ?transportDomainSctpDns - ], + AllTDomains = all_tdomains(), case lists:member(TDomain, SupportedTDomains) of true -> ok; @@ -388,7 +391,7 @@ check_tdomain(TDomain) -> %% --------- mk_tdomain(snmpUDPDomain) -> - ?snmpUDPDomain; + mk_tdomain(transportDomainUdpIpv4); mk_tdomain(transportDomainUdpIpv4) -> ?transportDomainUdpIpv4; mk_tdomain(transportDomainUdpIpv6) -> @@ -474,6 +477,26 @@ do_check_timer(WaitFor, Factor, Incr, Retry) -> %% --------- +all_domains() -> + [ + transportDomainUdpIpv4, + transportDomainUdpIpv6, + transportDomainUdpIpv4z, + transportDomainUdpIpv6z, + transportDomainTcpIpv4, + transportDomainTcpIpv6, + transportDomainTcpIpv4z, + transportDomainTcpIpv6z, + transportDomainSctpIpv4, + transportDomainSctpIpv6, + transportDomainSctpIpv4z, + transportDomainSctpIpv6z, + transportDomainLocal, + transportDomainUdpDns, + transportDomainTcpDns, + transportDomainSctpDns + ]. + check_domain(Domain) -> SupportedDomains = [ @@ -481,25 +504,7 @@ check_domain(Domain) -> transportDomainUdpIpv4, transportDomainUdpIpv6 ], - AllDomains = - [ - transportDomainUdpIpv4, - transportDomainUdpIpv6, - transportDomainUdpIpv4z, - transportDomainUdpIpv6z, - transportDomainTcpIpv4, - transportDomainTcpIpv6, - transportDomainTcpIpv4z, - transportDomainTcpIpv6z, - transportDomainSctpIpv4, - transportDomainSctpIpv6, - transportDomainSctpIpv4z, - transportDomainSctpIpv6z, - transportDomainLocal, - transportDomainUdpDns, - transportDomainTcpDns, - transportDomainSctpDns - ], + AllDomains = all_domains(), case lists:member(Domain, SupportedDomains) of true -> ok; diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl index fcbc6a88c9..6ab20e3e48 100644 --- a/lib/snmp/src/misc/snmp_config.erl +++ b/lib/snmp/src/misc/snmp_config.erl @@ -337,7 +337,7 @@ config_agent_sys() -> {dir, ATLDir}, {size, ATLSize}, {repair, ATLRepair}, - {seqno, ATLSeqNo}]}]; + {seqno, ATLSeqNo}]}]; no -> [] end, @@ -568,7 +568,7 @@ config_agent_snmp(Dir, Vsns) -> false -> ok end, - i("The following agent files were written: agent.conf, " + i("The following agent files where written: agent.conf, " "community.conf,~n" "standard.conf, target_addr.conf, " "target_params.conf, ~n" @@ -776,7 +776,7 @@ config_manager_snmp(Dir, Vsns) -> Users, Agents, Usms)) of ok -> i("~n- - - - - - - - - - - - -"), - i("The following manager files were written: " + i("The following manager files where written: " "manager.conf, agents.conf " ++ case lists:member(v3, Vsns) of true -> @@ -2350,7 +2350,9 @@ write_sys_config_file_manager_atl_opt(Fid, {type, Type}) -> write_sys_config_file_manager_atl_opt(Fid, {size, Size}) -> ok = io:format(Fid, "{size, ~w}", [Size]); write_sys_config_file_manager_atl_opt(Fid, {repair, Rep}) -> - ok = io:format(Fid, "{repair, ~w}", [Rep]). + ok = io:format(Fid, "{repair, ~w}", [Rep]); +write_sys_config_file_manager_atl_opt(Fid, {seqno, SeqNo}) -> + ok = io:format(Fid, "{seqno, ~w}", [SeqNo]). header() -> diff --git a/lib/snmp/test/snmp_compiler_test.erl b/lib/snmp/test/snmp_compiler_test.erl index 2e6020ae7a..cee11ba97a 100644 --- a/lib/snmp/test/snmp_compiler_test.erl +++ b/lib/snmp/test/snmp_compiler_test.erl @@ -47,6 +47,7 @@ module_identity/1, agent_capabilities/1, module_compliance/1, + warnings_as_errors/1, otp_6150/1, otp_8574/1, @@ -97,9 +98,10 @@ all() -> description, oid_conflicts, imports, - module_identity, - agent_capabilities, - module_compliance, + module_identity, + agent_capabilities, + module_compliance, + warnings_as_errors, {group, tickets} ]. @@ -152,6 +154,8 @@ description(Config) when is_list(Config) -> ok. +%%====================================================================== + oid_conflicts(suite) -> []; oid_conflicts(Config) when is_list(Config) -> put(tname,oid_conflicts), @@ -165,18 +169,24 @@ oid_conflicts(Config) when is_list(Config) -> ok. +%%====================================================================== + imports(suite) -> []; imports(Config) when is_list(Config) -> ?SKIP(not_yet_implemented). +%%====================================================================== + module_identity(suite) -> []; module_identity(Config) when is_list(Config) -> ?SKIP(not_yet_implemented). +%%====================================================================== + agent_capabilities(suite) -> []; agent_capabilities(Config) when is_list(Config) -> @@ -218,6 +228,8 @@ agent_capabilities(Config) when is_list(Config) -> ok. +%%====================================================================== + module_compliance(suite) -> []; module_compliance(Config) when is_list(Config) -> @@ -259,6 +271,31 @@ module_compliance(Config) when is_list(Config) -> ok. +%%====================================================================== + +warnings_as_errors(suite) -> + ["OTP-9437"]; +warnings_as_errors(Config) when is_list(Config) -> + put(tname,warnings_as_errors), + p("starting with Config: ~p~n", [Config]), + Dir = ?config(comp_dir, Config), + MibDir = ?config(mib_dir, Config), + MibFile = join(MibDir, "OTP8574-MIB.mib"), + OutFile = join(Dir, "OTP8574-MIB.bin"), + Opts = [{group_check, false}, + {outdir, Dir}, + {verbosity, trace}, + relaxed_row_name_assign_check], + {error, compilation_failed} = + snmpc:compile(MibFile, [warnings_as_errors|Opts]), + false = filelib:is_regular(OutFile), + {ok, _} = snmpc:compile(MibFile, Opts), + true = filelib:is_regular(OutFile), + ok. + + +%%====================================================================== + otp_6150(suite) -> []; otp_6150(Config) when is_list(Config) -> @@ -273,6 +310,8 @@ otp_6150(Config) when is_list(Config) -> ok. +%%====================================================================== + otp_8574(suite) -> []; otp_8574(Config) when is_list(Config) -> @@ -304,6 +343,8 @@ otp_8574(Config) when is_list(Config) -> end. +%%====================================================================== + otp_8595(suite) -> []; otp_8595(Config) when is_list(Config) -> diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 0b536748fb..d18f20d359 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -61,6 +61,7 @@ register_agent1/1, register_agent2/1, + register_agent3/1, info/1, @@ -383,12 +384,12 @@ end_per_testcase2(Case, Config) -> all() -> [ {group, start_and_stop_tests}, - {group, misc_tests}, + {group, misc_tests}, {group, user_tests}, - {group, agent_tests}, + {group, agent_tests}, {group, request_tests}, {group, event_tests}, - discovery, + discovery, {group, tickets} ]. @@ -417,7 +418,8 @@ groups() -> {agent_tests, [], [ register_agent1, - register_agent2 + register_agent2, + register_agent3 ] }, {request_tests, [], @@ -477,14 +479,14 @@ groups() -> }, {event_tests, [], [ - trap1, - trap2, - inform1, - inform2, - inform3, - inform4, - inform_swarm, - report + trap1%% , + %% trap2, + %% inform1, + %% inform2, + %% inform3, + %% inform4, + %% inform_swarm, + %% report ] }, {tickets, [], @@ -1134,6 +1136,7 @@ register_agent1(suite) -> register_agent1(Config) when is_list(Config) -> process_flag(trap_exit, true), put(tname,ra1), + p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1164,7 +1167,7 @@ register_agent1(Config) when is_list(Config) -> p("manager info: ~p~n", [mgr_info(ManagerNode)]), - p("register user(s) calvin & hobbe"), + p("register user(s) user_alfa & user_beta"), ?line ok = mgr_register_user(ManagerNode, user_alfa, snmpm_user_default, []), ?line ok = mgr_register_user(ManagerNode, user_beta, snmpm_user_default, []), p("manager info: ~p~n", [mgr_info(ManagerNode)]), @@ -1293,7 +1296,7 @@ register_agent2(Config) when is_list(Config) -> p("manager info: ~p~n", [mgr_info(ManagerNode)]), - p("register user(s) calvin & hobbe"), + p("register user(s) user_alfa & user_beta"), ?line ok = mgr_register_user(ManagerNode, user_alfa, snmpm_user_default, []), ?line ok = mgr_register_user(ManagerNode, user_beta, snmpm_user_default, []), p("manager info: ~p~n", [mgr_info(ManagerNode)]), @@ -1348,7 +1351,7 @@ register_agent2(Config) when is_list(Config) -> end, p("manager info: ~p~n", [mgr_info(ManagerNode)]), - + p("unregister user user_alfa"), ?line ok = mgr_unregister_user(ManagerNode, user_alfa), @@ -1377,7 +1380,157 @@ register_agent2(Config) when is_list(Config) -> p("manager info: ~p~n", [mgr_info(ManagerNode)]), - p("unregister user hobbe"), + p("unregister user user_beta"), + ?line ok = mgr_unregister_user(ManagerNode, user_beta), + + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + ?SLEEP(1000), + + p("stop snmp application (with only manager)"), + ?line ok = stop_snmp(ManagerNode), + + ?SLEEP(1000), + + stop_node(ManagerNode), + + ?SLEEP(1000), + + p("end"), + ok. + + +%%====================================================================== + +register_agent3(doc) -> + ["Test registration of agents with the NEW interface functions " + "and specifying transport domain"]; +register_agent3(suite) -> + []; +register_agent3(Config) when is_list(Config) -> + process_flag(trap_exit, true), + put(tname, ra3), + p("starting with Config: ~p~n", [Config]), + + ManagerNode = start_manager_node(), + + ConfDir = ?config(manager_conf_dir, Config), + DbDir = ?config(manager_db_dir, Config), + LocalHost = snmp_test_lib:localhost(), + + + write_manager_conf(ConfDir), + + Opts = [{server, [{verbosity, trace}]}, + {net_if, [{verbosity, trace}]}, + {note_store, [{verbosity, trace}]}, + {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}], + + + p("load snmp application"), + ?line ok = load_snmp(ManagerNode), + + p("set manager env for the snmp application"), + ?line ok = set_mgr_env(ManagerNode, Opts), + + p("starting snmp application (with only manager)"), + ?line ok = start_snmp(ManagerNode), + + p("started"), + + ?SLEEP(1000), + + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + p("register user(s) user_alfa & user_beta"), + ?line ok = mgr_register_user(ManagerNode, user_alfa, snmpm_user_default, []), + ?line ok = mgr_register_user(ManagerNode, user_beta, snmpm_user_default, []), + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + p("register agent(s)"), + TargetName1 = "agent2", + ?line ok = mgr_register_agent(ManagerNode, user_alfa, TargetName1, + [{tdomain, transportDomainUdpIpv4}, + {address, LocalHost}, + {port, 5001}, + {engine_id, "agentEngineId-1"}]), + TargetName2 = "agent3", + ?line ok = mgr_register_agent(ManagerNode, user_alfa, TargetName2, + [{tdomain, transportDomainUdpIpv6}, + {address, LocalHost}, + {port, 5002}, + {engine_id, "agentEngineId-2"}]), + TargetName3 = "agent4", + ?line {error, {unsupported_domain, _} = Reason4} = + mgr_register_agent(ManagerNode, user_beta, TargetName3, + [{tdomain, transportDomainTcpIpv4}, + {address, LocalHost}, + {port, 5003}, + {engine_id, "agentEngineId-3"}]), + p("Expected registration failure: ~p", [Reason4]), + TargetName4 = "agent5", + ?line {error, {unknown_domain, _} = Reason5} = + mgr_register_agent(ManagerNode, user_beta, TargetName4, + [{tdomain, transportDomainUdpIpv4_bad}, + {address, LocalHost}, + {port, 5004}, + {engine_id, "agentEngineId-4"}]), + p("Expected registration failure: ~p", [Reason5]), + + p("verify all agent(s): expect 2"), + case mgr_which_agents(ManagerNode) of + Agents1 when length(Agents1) =:= 2 -> + p("all agents: ~p~n", [Agents1]), + ok; + Agents1 -> + ?FAIL({agent_registration_failure, Agents1}) + end, + + p("verify user_alfa agent(s)"), + case mgr_which_agents(ManagerNode, user_alfa) of + Agents2 when length(Agents2) =:= 2 -> + p("calvin agents: ~p~n", [Agents2]), + ok; + Agents2 -> + ?FAIL({agent_registration_failure, Agents2}) + end, + + p("verify user_beta agent(s)"), + case mgr_which_agents(ManagerNode, user_beta) of + Agents3 when length(Agents3) =:= 0 -> + p("hobbe agents: ~p~n", [Agents3]), + ok; + Agents3 -> + ?FAIL({agent_registration_failure, Agents3}) + end, + + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + p("unregister user user_alfa"), + ?line ok = mgr_unregister_user(ManagerNode, user_alfa), + + p("verify all agent(s): expect 0"), + case mgr_which_agents(ManagerNode) of + Agents4 when length(Agents4) =:= 0 -> + p("all agents: ~p~n", [Agents4]), + ok; + Agents4 -> + ?FAIL({agent_unregistration_failure, Agents4}) + end, + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + p("verify all agent(s): expect 0"), + case mgr_which_agents(ManagerNode) of + [] -> + ok; + Agents5 -> + p("all agents: ~p~n", [Agents5]), + ?FAIL({agent_unregistration_failure, Agents5}) + end, + + p("manager info: ~p~n", [mgr_info(ManagerNode)]), + + p("unregister user user_beta"), ?line ok = mgr_unregister_user(ManagerNode, user_beta), p("manager info: ~p~n", [mgr_info(ManagerNode)]), diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk index 29228fc59b..08251ab9ea 100644 --- a/lib/snmp/vsn.mk +++ b/lib/snmp/vsn.mk @@ -17,6 +17,6 @@ # # %CopyrightEnd% -SNMP_VSN = 4.20 +SNMP_VSN = 4.21 PRE_VSN = APP_VSN = "snmp-$(SNMP_VSN)$(PRE_VSN)" diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 71f3941577..6fc4fdc43d 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -29,6 +29,20 @@ <file>notes.xml</file> </header> +<section><title>Ssh 2.0.8</title> + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Calling ssh_sftp:stop_channel/1 resulted in that the trap_exit flag was + set to true for the invoking process.</p> + <p> + Own Id: OTP-9386 Aux Id: seq11865</p> + </item> + </list> + </section> +</section> + <section><title>Ssh 2.0.7</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 974145836c..150b7d86dd 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,13 +19,19 @@ {"%VSN%", [ - {"2.0.6", [{load_module, ssh_userreg, soft_purge, soft_purge, []}]}, + {"2.0.7", [{load_module, ssh_sftp, soft_purge, soft_purge, []}]}, + {"2.0.6", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_sftp, soft_purge, soft_purge, []}]}, {"2.0.5", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_sftp, soft_purge, soft_purge, []}, {load_module, ssh_connection_handler, soft_purge, soft_purge, [ssh_userreg]}]} ], [ - {"2.0.6", [{load_module, ssh_userreg, soft_purge, soft_purge, []}]}, + {"2.0.7", [{load_module, ssh_sftp, soft_purge, soft_purge, []}]}, + {"2.0.6", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_sftp, soft_purge, soft_purge, []}]}, {"2.0.5", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_sftp, soft_purge, soft_purge, []}, {load_module, ssh_connection_handler, soft_purge, soft_purge, [ssh_userreg]}]} ] }. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 59e09fdd0f..f000558100 100755 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% Copyright Ericsson AB 2005-2011. 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 @@ -130,9 +130,9 @@ start_channel(Host, Port, Opts) -> end. stop_channel(Pid) -> - case process_info(Pid, [trap_exit]) of - [{trap_exit, Bool}] -> - process_flag(trap_exit, true), + case is_process_alive(Pid) of + true -> + OldValue = process_flag(trap_exit, true), link(Pid), exit(Pid, ssh_sftp_stop_channel), receive @@ -145,9 +145,9 @@ stop_channel(Pid) -> ok end end, - process_flag(trap_exit, Bool), + process_flag(trap_exit, OldValue), ok; - undefined -> + false -> ok end. diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index d79038df29..fe2b915d17 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 2.0.7 +SSH_VSN = 2.0.8 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/c_src/esock_openssl.c b/lib/ssl/c_src/esock_openssl.c index 2621c9934e..0bc42958f0 100644 --- a/lib/ssl/c_src/esock_openssl.c +++ b/lib/ssl/c_src/esock_openssl.c @@ -1024,7 +1024,7 @@ static void info_callback(const SSL *ssl, int where, int ret) } } -/* This function is called whenever a SSL_CTX *ctx structure is +/* This function is called whenever an SSL_CTX *ctx structure is * freed. */ static void callback_data_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index b2d17925fd..e090b4e1ef 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -554,7 +554,7 @@ Own Id: OTP-8224</p> </item> <item> - <p>A ssl:ssl_accept/3 could crash a connection if the + <p>An ssl:ssl_accept/3 could crash a connection if the timing was wrong.</p> <p>Removed info message if the socket closed without a proper disconnect from the ssl layer. </p> <p>ssl:send/2 is now blocking until the @@ -770,7 +770,7 @@ <item> <p> The new ssl implementation released as a alfa in this - version supports upgrading of a tcp connection to a ssl + version supports upgrading of a tcp connection to an ssl connection so that http client and servers may implement RFC 2817.</p> <p> @@ -789,7 +789,7 @@ very crippled as the control of the ssl-socket was deep down in openssl making it hard if not impossible to support all inet options, ipv6 and upgrade of a tcp - connection to a ssl connection. The alfa version has a + connection to an ssl connection. The alfa version has a few limitations that will be removed before the ssl-4.0 release. Main differences and limitations in the alfa are listed below.</p> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 0da6bbee5b..0c4c8796be 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -35,7 +35,7 @@ <title>SSL</title> <list type="bulleted"> - <item>ssl requires the crypto an public_key applications.</item> + <item>ssl requires the crypto and public_key applications.</item> <item>Supported SSL/TLS-versions are SSL-3.0 and TLS-1.0 </item> <item>For security reasons sslv2 is not supported.</item> <item>Ephemeral Diffie-Hellman cipher suites are supported @@ -216,7 +216,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | application is encountered. Additionally it will be called when a certificate is considered valid by the path validation to allow access to each certificate in the path to the user - application. Note that the it will differentiate between the + application. Note that it will differentiate between the peer certificate and CA certificates by using valid_peer or valid as the second argument to the verify fun. See <seealso marker="public_key:cert_records">the public_key User's @@ -326,10 +326,10 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | </item> <tag>{fail_if_no_peer_cert, boolean()}</tag> - <item>Used together with {verify, verify_peer} by a ssl server. + <item>Used together with {verify, verify_peer} by an ssl server. If set to true, the server will fail if the client does not have a certificate to send, i.e. sends a empty certificate, if set to - false it will only fail if the client sends a invalid + false it will only fail if the client sends an invalid certificate (an empty certificate is considered valid). </item> @@ -343,10 +343,10 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | PeerCert, Compression, CipherSuite) -> boolean()}</tag> <item>Enables the ssl server to have a local policy for deciding if a session should be reused or not, - only meaning full if <c>reuse_sessions</c> is set to true. + only meaningful if <c>reuse_sessions</c> is set to true. SuggestedSessionId is a binary(), PeerCert is a DER encoded certificate, Compression is an enumeration integer - and CipherSuite of type ciphersuite(). + and CipherSuite is of type ciphersuite(). </item> </taglist> @@ -355,7 +355,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <section> <title>General</title> - <p>When a ssl socket is in active mode (the default), data from the + <p>When an ssl socket is in active mode (the default), data from the socket is delivered to the owner of the socket in the form of messages: </p> @@ -396,7 +396,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary> Upgrades a gen_tcp, or - equivalent, connected socket to a ssl socket. </fsummary> + equivalent, connected socket to an ssl socket. </fsummary> <type> <v>Socket = socket()</v> <v>SslOptions = [ssloption()]</v> @@ -405,7 +405,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <v>Reason = term()</v> </type> <desc> <p>Upgrades a gen_tcp, or equivalent, - connected socket to a ssl socket i.e. performs the + connected socket to an ssl socket i.e. performs the client-side ssl handshake.</p> </desc> </func> @@ -428,12 +428,12 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <func> <name>close(SslSocket) -> ok | {error, Reason}</name> - <fsummary>Close a ssl connection</fsummary> + <fsummary>Close an ssl connection</fsummary> <type> <v>SslSocket = sslsocket()</v> <v>Reason = term()</v> </type> - <desc><p>Close a ssl connection.</p> + <desc><p>Close an ssl connection.</p> </desc> </func> @@ -450,7 +450,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <v>Reason = term()</v> </type> <desc><p>Assigns a new controlling process to the ssl-socket. A - controlling process is the owner of a ssl-socket, and receives + controlling process is the owner of an ssl-socket, and receives all messages from the socket.</p> </desc> </func> @@ -480,7 +480,6 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | </func> <func> - <name>getopts(Socket) -> </name> <name>getopts(Socket, OptionNames) -> {ok, [socketoption()]} | {error, Reason}</name> <fsummary>Get the value of the specified options.</fsummary> @@ -489,8 +488,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <v>OptionNames = [atom()]</v> </type> <desc> - <p>Get the value of the specified socket options, if no - options are specified all options are returned. + <p>Get the value of the specified socket options. </p> </desc> </func> @@ -498,14 +496,14 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <func> <name>listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name> - <fsummary>Creates a ssl listen socket.</fsummary> + <fsummary>Creates an ssl listen socket.</fsummary> <type> <v>Port = integer()</v> <v>Options = options()</v> <v>ListenSocket = sslsocket()</v> </type> <desc> - <p>Creates a ssl listen socket.</p> + <p>Creates an ssl listen socket.</p> </desc> </func> @@ -589,6 +587,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | the socket is closed.</p> </desc> </func> + <func> <name>setopts(Socket, Options) -> ok | {error, Reason}</name> <fsummary>Set socket options.</fsummary> @@ -648,7 +647,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | </type> <desc> <p> Upgrades a gen_tcp, or - equivalent, socket to a ssl socket i.e. performs the + equivalent, socket to an ssl socket i.e. performs the ssl server-side handshake.</p> <p><warning>Note that the listen socket should be in {active, false} mode before telling the client that the server is ready to upgrade diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml index 6936408881..ca5cc8bc7a 100644 --- a/lib/ssl/doc/src/ssl_protocol.xml +++ b/lib/ssl/doc/src/ssl_protocol.xml @@ -31,11 +31,11 @@ </p> <p>By default erlang ssl is run over the TCP/IP protocol even - though you could plug in an other reliable transport protocol + though you could plug in any other reliable transport protocol with the same API as gen_tcp.</p> <p>If a client and server wants to use an upgrade mechanism, such as - defined by RFC2817, to upgrade a regular TCP/IP connection to a ssl + defined by RFC2817, to upgrade a regular TCP/IP connection to an ssl connection the erlang ssl API supports this. This can be useful for things such as supporting HTTP and HTTPS on the same port and implementing virtual hosting. diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml index 605290b6f9..ab837a156a 100644 --- a/lib/ssl/doc/src/using_ssl.xml +++ b/lib/ssl/doc/src/using_ssl.xml @@ -56,7 +56,7 @@ <code type="erl">1 server> ssl:start(). ok</code> - <p>Create a ssl listen socket</p> + <p>Create an ssl listen socket</p> <code type="erl">2 server> {ok, ListenSocket} = ssl:listen(9999, [{certfile, "cert.pem"}, {keyfile, "key.pem"},{reuseaddr, true}]). {ok,{sslsocket, [...]}}</code> @@ -90,7 +90,7 @@ ok</code> <section> <title>Upgrade example</title> - <note><p> To upgrade a TCP/IP connection to a ssl connection the + <note><p> To upgrade a TCP/IP connection to an ssl connection the client and server have to aggre to do so. Agreement may be accompliced by using a protocol such the one used by HTTP specified in RFC 2817.</p> </note> @@ -114,7 +114,7 @@ ok</code> <code type="erl">2 client> {ok, Socket} = gen_tcp:connect("localhost", 9999, [], infinity).</code> <p>Make sure active is set to false before trying - to upgrade a connection to a ssl connection, otherwhise + to upgrade a connection to an ssl connection, otherwhise ssl handshake messages may be deliverd to the wrong process.</p> <code type="erl">4 server> inet:setopts(Socket, [{active, false}]). ok</code> @@ -124,7 +124,7 @@ ok</code> {certfile, "cert.pem"}, {keyfile, "key.pem"}]). {ok,{sslsocket,[...]}}</code> - <p> Upgrade to a ssl connection. Note that the client and server + <p> Upgrade to an ssl connection. Note that the client and server must agree upon the upgrade and the server must call ssl:accept/2 before the client calls ssl:connect/3.</p> <code type="erl">3 client>{ok, SSLSocket} = ssl:connect(Socket, [{cacertfile, "cacerts.pem"}, diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index cf8867245b..29674f30da 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,6 +1,7 @@ %% -*- erlang -*- {"%VSN%", [ + {"4.1.5", [{restart_application, ssl}]}, {"4.1.4", [{restart_application, ssl}]}, {"4.1.3", [{restart_application, ssl}]}, {"4.1.2", [{restart_application, ssl}]}, @@ -9,6 +10,7 @@ {"4.0.1", [{restart_application, ssl}]} ], [ + {"4.1.5", [{restart_application, ssl}]}, {"4.1.4", [{restart_application, ssl}]}, {"4.1.3", [{restart_application, ssl}]}, {"4.1.2", [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index a5e8e7e5c2..46e4b98c98 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -104,7 +104,7 @@ stop() -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Connect to a ssl server. +%% Description: Connect to an ssl server. %%-------------------------------------------------------------------- connect(Socket, SslOptions) when is_port(Socket) -> connect(Socket, SslOptions, infinity). @@ -112,7 +112,7 @@ connect(Socket, SslOptions) when is_port(Socket) -> connect(Socket, SslOptions0, Timeout) when is_port(Socket) -> EmulatedOptions = emulated_options(), {ok, InetValues} = inet:getopts(Socket, EmulatedOptions), - inet:setopts(Socket, internal_inet_values()), + ok = inet:setopts(Socket, internal_inet_values()), try handle_options(SslOptions0 ++ InetValues, client) of {ok, #config{cb=CbInfo, ssl=SslOptions, emulated=EmOpts}} -> case inet:peername(Socket) of @@ -151,7 +151,7 @@ connect(Host, Port, Options0, Timeout) -> -spec listen(port_num(), [option()]) ->{ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Creates a ssl listen socket. +%% Description: Creates an ssl listen socket. %%-------------------------------------------------------------------- listen(_Port, []) -> {error, enooptions}; @@ -177,7 +177,7 @@ listen(Port, Options0) -> -spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Performs transport accept on a ssl listen socket +%% Description: Performs transport accept on an ssl listen socket %%-------------------------------------------------------------------- transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). @@ -218,7 +218,7 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) -> ok | {ok, #sslsocket{}} | {error, reason()}. -spec ssl_accept(port(), [option()], timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Performs accept on a ssl listen socket. e.i. performs +%% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- ssl_accept(ListenSocket) -> @@ -238,7 +238,7 @@ ssl_accept(#sslsocket{} = Socket, Timeout) -> ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> EmulatedOptions = emulated_options(), {ok, InetValues} = inet:getopts(Socket, EmulatedOptions), - inet:setopts(Socket, internal_inet_values()), + ok = inet:setopts(Socket, internal_inet_values()), try handle_options(SslOptions ++ InetValues, server) of {ok, #config{cb=CbInfo,ssl=SslOpts, emulated=EmOpts}} -> {ok, Port} = inet:port(Socket), @@ -252,7 +252,7 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> %%-------------------------------------------------------------------- -spec close(#sslsocket{}) -> term(). %% -%% Description: Close a ssl connection +%% Description: Close an ssl connection %%-------------------------------------------------------------------- close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}, fd = new_ssl}) -> CbMod:close(ListenSocket); @@ -406,25 +406,51 @@ cipher_suites(openssl) -> %% %% Description: Gets options %%-------------------------------------------------------------------- -getopts(#sslsocket{fd = new_ssl, pid = Pid}, OptTags) when is_pid(Pid) -> - ssl_connection:get_opts(Pid, OptTags); -getopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, OptTags) -> - inet:getopts(ListenSocket, OptTags); -getopts(#sslsocket{} = Socket, Options) -> +getopts(#sslsocket{fd = new_ssl, pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> + ssl_connection:get_opts(Pid, OptionTags); +getopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, OptionTags) when is_list(OptionTags) -> + try inet:getopts(ListenSocket, OptionTags) of + {ok, _} = Result -> + Result; + {error, InetError} -> + {error, {eoptions, {inet_options, OptionTags, InetError}}} + catch + _:_ -> + {error, {eoptions, {inet_options, OptionTags}}} + end; +getopts(#sslsocket{fd = new_ssl}, OptionTags) -> + {error, {eoptions, {inet_options, OptionTags}}}; +getopts(#sslsocket{} = Socket, OptionTags) -> ensure_old_ssl_started(), - ssl_broker:getopts(Socket, Options). + ssl_broker:getopts(Socket, OptionTags). %%-------------------------------------------------------------------- -spec setopts(#sslsocket{}, [proplists:property()]) -> ok | {error, reason()}. %% %% Description: Sets options %%-------------------------------------------------------------------- -setopts(#sslsocket{fd = new_ssl, pid = Pid}, Opts0) when is_pid(Pid) -> - Opts = proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Opts0), - ssl_connection:set_opts(Pid, Opts); -setopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, OptTags) -> - inet:setopts(ListenSocket, OptTags); +setopts(#sslsocket{fd = new_ssl, pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> + try proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Options0) of + Options -> + ssl_connection:set_opts(Pid, Options) + catch + _:_ -> + {error, {eoptions, {not_a_proplist, Options0}}} + end; + +setopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, Options) when is_list(Options) -> + try inet:setopts(ListenSocket, Options) of + ok -> + ok; + {error, InetError} -> + {error, {eoptions, {inet_options, Options, InetError}}} + catch + _:Error -> + {error, {eoptions, {inet_options, Options, Error}}} + end; +setopts(#sslsocket{fd = new_ssl}, Options) -> + {error, {eoptions,{not_a_proplist, Options}}}; setopts(#sslsocket{} = Socket, Options) -> ensure_old_ssl_started(), ssl_broker:setopts(Socket, Options). diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 8c0c2bfa5d..422ea6404b 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -30,9 +30,9 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/2, - certificate_chain/2, - file_to_certificats/1, +-export([trusted_cert_and_path/3, + certificate_chain/3, + file_to_certificats/2, validate_extension/3, is_valid_extkey_usage/2, is_valid_key_usage/2, @@ -46,14 +46,14 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec trusted_cert_and_path([der_cert()], certdb_ref()) -> +-spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref()) -> {der_cert() | unknown_ca, [der_cert()]}. %% %% Description: Extracts the root cert (if not presents tries to %% look it up, if not found {bad_cert, unknown_ca} will be added verification %% errors. Returns {RootCert, Path, VerifyErrors} %%-------------------------------------------------------------------- -trusted_cert_and_path(CertChain, CertDbRef) -> +trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef) -> Path = [Cert | _] = lists:reverse(CertChain), OtpCert = public_key:pkix_decode_cert(Cert, otp), SignedAndIssuerID = @@ -66,7 +66,7 @@ trusted_cert_and_path(CertChain, CertDbRef) -> {ok, IssuerId} -> {other, IssuerId}; {error, issuer_not_found} -> - case find_issuer(OtpCert, no_candidate) of + case find_issuer(OtpCert, no_candidate, CertDbHandle) of {ok, IssuerId} -> {other, IssuerId}; Other -> @@ -82,7 +82,7 @@ trusted_cert_and_path(CertChain, CertDbRef) -> {self, _} when length(Path) == 1 -> {selfsigned_peer, Path}; {_ ,{SerialNr, Issuer}} -> - case ssl_manager:lookup_trusted_cert(CertDbRef, SerialNr, Issuer) of + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of {ok, {BinCert,_}} -> {BinCert, Path}; _ -> @@ -92,23 +92,23 @@ trusted_cert_and_path(CertChain, CertDbRef) -> end. %%-------------------------------------------------------------------- --spec certificate_chain(undefined | binary(), certdb_ref()) -> +-spec certificate_chain(undefined | binary(), db_handle(), certdb_ref()) -> {error, no_cert} | {ok, [der_cert()]}. %% %% Description: Return the certificate chain to send to peer. %%-------------------------------------------------------------------- -certificate_chain(undefined, _CertsDbRef) -> +certificate_chain(undefined, _, _) -> {error, no_cert}; -certificate_chain(OwnCert, CertsDbRef) -> +certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> ErlCert = public_key:pkix_decode_cert(OwnCert, otp), - certificate_chain(ErlCert, OwnCert, CertsDbRef, [OwnCert]). + certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]). %%-------------------------------------------------------------------- --spec file_to_certificats(string()) -> [der_cert()]. +-spec file_to_certificats(string(), term()) -> [der_cert()]. %% %% Description: Return list of DER encoded certificates. %%-------------------------------------------------------------------- -file_to_certificats(File) -> - {ok, List} = ssl_manager:cache_pem_file(File), +file_to_certificats(File, DbHandle) -> + {ok, List} = ssl_manager:cache_pem_file(File, DbHandle), [Bin || {'Certificate', Bin, not_encrypted} <- List]. %%-------------------------------------------------------------------- -spec validate_extension(term(), #'Extension'{} | {bad_cert, atom()} | valid, @@ -180,7 +180,7 @@ signature_type(?'id-dsa-with-sha1') -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -certificate_chain(OtpCert, _Cert, CertsDbRef, Chain) -> +certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> IssuerAndSelfSigned = case public_key:pkix_is_self_signed(OtpCert) of true -> @@ -191,11 +191,11 @@ certificate_chain(OtpCert, _Cert, CertsDbRef, Chain) -> case IssuerAndSelfSigned of {_, true = SelfSigned} -> - certificate_chain(CertsDbRef, Chain, ignore, ignore, SelfSigned); + certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, no_candidate) of + case find_issuer(OtpCert, no_candidate, CertDbHandle) of {ok, {SerialNr, Issuer}} -> - certificate_chain(CertsDbRef, Chain, + certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned); _ -> %% Guess the the issuer must be the root @@ -205,19 +205,19 @@ certificate_chain(OtpCert, _Cert, CertsDbRef, Chain) -> {ok, lists:reverse(Chain)} end; {{ok, {SerialNr, Issuer}}, SelfSigned} -> - certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, SelfSigned) + certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned) end. -certificate_chain(_CertsDbRef, Chain, _SerialNr, _Issuer, true) -> +certificate_chain(_,_, Chain, _SerialNr, _Issuer, true) -> {ok, lists:reverse(Chain)}; -certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> - case ssl_manager:lookup_trusted_cert(CertsDbRef, +certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertsDbRef, SerialNr, Issuer) of {ok, {IssuerCert, ErlCert}} -> ErlCert = public_key:pkix_decode_cert(IssuerCert, otp), certificate_chain(ErlCert, IssuerCert, - CertsDbRef, [IssuerCert | Chain]); + CertDbHandle, CertsDbRef, [IssuerCert | Chain]); _ -> %% The trusted cert may be obmitted from the chain as the %% counter part needs to have it anyway to be able to @@ -227,8 +227,8 @@ certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> {ok, lists:reverse(Chain)} end. -find_issuer(OtpCert, PrevCandidateKey) -> - case ssl_manager:issuer_candidate(PrevCandidateKey) of +find_issuer(OtpCert, PrevCandidateKey, CertDbHandle) -> + case ssl_manager:issuer_candidate(PrevCandidateKey, CertDbHandle) of no_more_candidates -> {error, issuer_not_found}; {Key, {_Cert, ErlCertCandidate}} -> @@ -236,7 +236,7 @@ find_issuer(OtpCert, PrevCandidateKey) -> true -> public_key:pkix_issuer_id(ErlCertCandidate, self); false -> - find_issuer(OtpCert, Key) + find_issuer(OtpCert, Key, CertDbHandle) end end. diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl index 3eceefa304..0560a02110 100644 --- a/lib/ssl/src/ssl_certificate_db.erl +++ b/lib/ssl/src/ssl_certificate_db.erl @@ -26,8 +26,8 @@ -include_lib("public_key/include/public_key.hrl"). -export([create/0, remove/1, add_trusted_certs/3, - remove_trusted_certs/2, lookup_trusted_cert/3, issuer_candidate/1, - lookup_cached_certs/1, cache_pem_file/4, uncache_pem_file/2, lookup/2]). + remove_trusted_certs/2, lookup_trusted_cert/4, issuer_candidate/2, + lookup_cached_certs/2, cache_pem_file/4, uncache_pem_file/2, lookup/2]). -type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}. @@ -36,19 +36,19 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec create() -> certdb_ref(). +-spec create() -> [db_handle()]. %% %% Description: Creates a new certificate db. -%% Note: lookup_trusted_cert/3 may be called from any process but only +%% Note: lookup_trusted_cert/4 may be called from any process but only %% the process that called create may call the other functions. %%-------------------------------------------------------------------- create() -> - [ets:new(certificate_db_name(), [named_table, set, protected]), - ets:new(ssl_file_to_ref, [named_table, set, protected]), + [ets:new(ssl_otp_certificate_db, [set, protected]), + ets:new(ssl_file_to_ref, [set, protected]), ets:new(ssl_pid_to_file, [bag, private])]. %%-------------------------------------------------------------------- --spec remove(certdb_ref()) -> term(). +-spec remove([db_handle()]) -> term(). %% %% Description: Removes database db %%-------------------------------------------------------------------- @@ -56,7 +56,7 @@ remove(Dbs) -> lists:foreach(fun(Db) -> true = ets:delete(Db) end, Dbs). %%-------------------------------------------------------------------- --spec lookup_trusted_cert(reference(), serialnumber(), issuer()) -> +-spec lookup_trusted_cert(db_handle(), certdb_ref(), serialnumber(), issuer()) -> undefined | {ok, {der_cert(), #'OTPCertificate'{}}}. %% @@ -64,19 +64,19 @@ remove(Dbs) -> %% <SerialNumber, Issuer>. Ref is used as it is specified %% for each connection which certificates are trusted. %%-------------------------------------------------------------------- -lookup_trusted_cert(Ref, SerialNumber, Issuer) -> - case lookup({Ref, SerialNumber, Issuer}, certificate_db_name()) of +lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> + case lookup({Ref, SerialNumber, Issuer}, DbHandle) of undefined -> undefined; [Certs] -> {ok, Certs} end. -lookup_cached_certs(File) -> - ets:lookup(certificate_db_name(), {file, File}). +lookup_cached_certs(DbHandle, File) -> + ets:lookup(DbHandle, {file, File}). %%-------------------------------------------------------------------- --spec add_trusted_certs(pid(), string() | {der, list()}, certdb_ref()) -> {ok, certdb_ref()}. +-spec add_trusted_certs(pid(), string() | {der, list()}, [db_handle()]) -> {ok, [db_handle()]}. %% %% Description: Adds the trusted certificates from file <File> to the %% runtime database. Returns Ref that should be handed to lookup_trusted_cert @@ -100,7 +100,7 @@ add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) -> insert(Pid, File, PidToFileDb), {ok, Ref}. %%-------------------------------------------------------------------- --spec cache_pem_file(pid(), string(), time(), certdb_ref()) -> term(). +-spec cache_pem_file(pid(), string(), time(), [db_handle()]) -> term(). %% %% Description: Cache file as binary in DB %%-------------------------------------------------------------------- @@ -112,7 +112,7 @@ cache_pem_file(Pid, File, Time, [CertsDb, _FileToRefDb, PidToFileDb]) -> {ok, Content}. %-------------------------------------------------------------------- --spec uncache_pem_file(string(), certdb_ref()) -> no_return(). +-spec uncache_pem_file(string(), [db_handle()]) -> no_return(). %% %% Description: If a cached file is no longer valid (changed on disk) %% we must terminate the connections using the old file content, and @@ -130,7 +130,7 @@ uncache_pem_file(File, [_CertsDb, _FileToRefDb, PidToFileDb]) -> %%-------------------------------------------------------------------- --spec remove_trusted_certs(pid(), certdb_ref()) -> term(). +-spec remove_trusted_certs(pid(), [db_handle()]) -> term(). %% %% Description: Removes trusted certs originating from @@ -161,7 +161,7 @@ remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) -> end. %%-------------------------------------------------------------------- --spec issuer_candidate(no_candidate | cert_key() | {file, term()}) -> +-spec issuer_candidate(no_candidate | cert_key() | {file, term()}, term()) -> {cert_key(),{der_cert(), #'OTPCertificate'{}}} | no_more_candidates. %% %% Description: If a certificat does not define its issuer through @@ -169,32 +169,30 @@ remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) -> %% try to find the issuer in the database over known %% certificates. %%-------------------------------------------------------------------- -issuer_candidate(no_candidate) -> - Db = certificate_db_name(), +issuer_candidate(no_candidate, Db) -> case ets:first(Db) of '$end_of_table' -> no_more_candidates; {file, _} = Key -> - issuer_candidate(Key); + issuer_candidate(Key, Db); Key -> [Cert] = lookup(Key, Db), {Key, Cert} end; -issuer_candidate(PrevCandidateKey) -> - Db = certificate_db_name(), +issuer_candidate(PrevCandidateKey, Db) -> case ets:next(Db, PrevCandidateKey) of '$end_of_table' -> no_more_candidates; {file, _} = Key -> - issuer_candidate(Key); + issuer_candidate(Key, Db); Key -> [Cert] = lookup(Key, Db), {Key, Cert} end. %%-------------------------------------------------------------------- --spec lookup(term(), term()) -> term() | undefined. +-spec lookup(term(), db_handle()) -> term() | undefined. %% %% Description: Looks up an element in a certificat <Db>. %%-------------------------------------------------------------------- @@ -212,9 +210,6 @@ lookup(Key, Db) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -certificate_db_name() -> - ssl_otp_certificate_db. - insert(Key, Data, Db) -> true = ets:insert(Db, {Key, Data}). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 2c452837f8..79570c520a 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -70,6 +70,7 @@ %% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary()) tls_handshake_hashes, % see above tls_cipher_texts, % list() received but not deciphered yet + cert_db, % session, % #session{} from ssl_handshake.hrl session_cache, % session_cache_cb, % @@ -130,7 +131,7 @@ recv(Pid, Length, Timeout) -> pid(), tuple(), timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Connect to a ssl server. +%% Description: Connect to an ssl server. %%-------------------------------------------------------------------- connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> try start_fsm(client, Host, Port, Socket, Options, User, CbInfo, @@ -144,7 +145,7 @@ connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> pid(), tuple(), timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% -%% Description: Performs accept on a ssl listen socket. e.i. performs +%% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) -> @@ -184,7 +185,7 @@ socket_control(Socket, Pid, CbModule) -> %%-------------------------------------------------------------------- -spec close(pid()) -> ok | {error, reason()}. %% -%% Description: Close a ssl connection +%% Description: Close an ssl connection %%-------------------------------------------------------------------- close(ConnectionPid) -> case sync_send_all_state_event(ConnectionPid, close) of @@ -305,12 +306,13 @@ init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, Hashes0 = ssl_handshake:init_hashes(), try ssl_init(SSLOpts0, Role) of - {ok, Ref, CacheRef, OwnCert, Key, DHParams} -> + {ok, Ref, CertDbHandle, CacheHandle, OwnCert, Key, DHParams} -> Session = State0#state.session, State = State0#state{tls_handshake_hashes = Hashes0, session = Session#session{own_certificate = OwnCert}, cert_db_ref = Ref, - session_cache = CacheRef, + cert_db = CertDbHandle, + session_cache = CacheHandle, private_key = Key, diffie_hellman_params = DHParams}, {ok, hello, State, get_timeout(State)} @@ -500,9 +502,10 @@ certify(#certificate{asn1_certificates = []}, certify(#certificate{} = Cert, #state{negotiated_version = Version, role = Role, + cert_db = CertDbHandle, cert_db_ref = CertDbRef, ssl_options = Opts} = State) -> - case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth, + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, Opts#ssl_options.verify, Opts#ssl_options.verify_fun, Role) of {PeerCert, PublicKeyInfo} -> @@ -859,23 +862,23 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, #state{socket_options = Opts1, socket = Socket, user_data_buffer = Buffer} = State0) -> - Opts = set_socket_opts(Socket, Opts0, Opts1, []), + {Reply, Opts} = set_socket_opts(Socket, Opts0, Opts1, []), State1 = State0#state{socket_options = Opts}, if Opts#socket_options.active =:= false -> - {reply, ok, StateName, State1, get_timeout(State1)}; + {reply, Reply, StateName, State1, get_timeout(State1)}; Buffer =:= <<>>, Opts1#socket_options.active =:= false -> %% Need data, set active once {Record, State2} = next_record_if_active(State1), case next_state(StateName, Record, State2) of {next_state, StateName, State, Timeout} -> - {reply, ok, StateName, State, Timeout}; + {reply, Reply, StateName, State, Timeout}; {stop, Reason, State} -> {stop, Reason, State} end; Buffer =:= <<>> -> %% Active once already set - {reply, ok, StateName, State1, get_timeout(State1)}; + {reply, Reply, StateName, State1, get_timeout(State1)}; true -> case application_data(<<>>, State1) of Stop = {stop,_,_} -> @@ -883,7 +886,7 @@ handle_sync_event({set_opts, Opts0}, _From, StateName, {Record, State2} -> case next_state(StateName, Record, State2) of {next_state, StateName, State, Timeout} -> - {reply, ok, StateName, State, Timeout}; + {reply, Reply, StateName, State, Timeout}; {stop, Reason, State} -> {stop, Reason, State} end @@ -1044,19 +1047,19 @@ start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_, _} = CbInfo, end. ssl_init(SslOpts, Role) -> - {ok, CertDbRef, CacheRef, OwnCert} = init_certificates(SslOpts, Role), + {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), PrivateKey = - init_private_key(SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, + init_private_key(CertDbHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), - {ok, CertDbRef, CacheRef, OwnCert, PrivateKey, DHParams}. + DHParams = init_diffie_hellman(CertDbHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), + {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. init_certificates(#ssl_options{cacerts = CaCerts, cacertfile = CACertFile, certfile = CertFile, cert = Cert}, Role) -> - {ok, CertDbRef, CacheRef} = + {ok, CertDbRef, CertDbHandle, CacheHandle} = try Certs = case CaCerts of undefined -> @@ -1064,44 +1067,44 @@ init_certificates(#ssl_options{cacerts = CaCerts, _ -> {der, CaCerts} end, - {ok, _, _} = ssl_manager:connection_init(Certs, Role) + {ok, _, _, _} = ssl_manager:connection_init(Certs, Role) catch Error:Reason -> handle_file_error(?LINE, Error, Reason, CACertFile, ecacertfile, erlang:get_stacktrace()) end, - init_certificates(Cert, CertDbRef, CacheRef, CertFile, Role). + init_certificates(Cert, CertDbRef, CertDbHandle, CacheHandle, CertFile, Role). -init_certificates(undefined, CertDbRef, CacheRef, "", _) -> - {ok, CertDbRef, CacheRef, undefined}; +init_certificates(undefined, CertDbRef, CertDbHandle, CacheHandle, "", _) -> + {ok, CertDbRef, CertDbHandle, CacheHandle, undefined}; -init_certificates(undefined, CertDbRef, CacheRef, CertFile, client) -> +init_certificates(undefined, CertDbRef, CertDbHandle, CacheHandle, CertFile, client) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile), - {ok, CertDbRef, CacheRef, OwnCert} + [OwnCert] = ssl_certificate:file_to_certificats(CertFile, CertDbHandle), + {ok, CertDbRef, CertDbHandle, CacheHandle, OwnCert} catch _Error:_Reason -> - {ok, CertDbRef, CacheRef, undefined} + {ok, CertDbRef, CertDbHandle, CacheHandle, undefined} end; -init_certificates(undefined, CertDbRef, CacheRef, CertFile, server) -> +init_certificates(undefined, CertDbRef, CertDbHandle, CacheRef, CertFile, server) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile), - {ok, CertDbRef, CacheRef, OwnCert} + [OwnCert] = ssl_certificate:file_to_certificats(CertFile, CertDbHandle), + {ok, CertDbRef, CertDbHandle, CacheRef, OwnCert} catch Error:Reason -> handle_file_error(?LINE, Error, Reason, CertFile, ecertfile, erlang:get_stacktrace()) end; -init_certificates(Cert, CertDbRef, CacheRef, _, _) -> - {ok, CertDbRef, CacheRef, Cert}. +init_certificates(Cert, CertDbRef, CertDbHandle, CacheRef, _, _) -> + {ok, CertDbRef, CertDbHandle, CacheRef, Cert}. -init_private_key(undefined, "", _Password, _Client) -> +init_private_key(_, undefined, "", _Password, _Client) -> undefined; -init_private_key(undefined, KeyFile, Password, _) -> +init_private_key(DbHandle, undefined, KeyFile, Password, _) -> try - {ok, List} = ssl_manager:cache_pem_file(KeyFile), + {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle), [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, - PKey =:= 'RSAPrivateKey' orelse + PKey =:= 'RSAPrivateKey' orelse PKey =:= 'DSAPrivateKey'], public_key:pem_entry_decode(PemEntry, Password) catch @@ -1110,9 +1113,9 @@ init_private_key(undefined, KeyFile, Password, _) -> erlang:get_stacktrace()) end; -init_private_key({rsa, PrivateKey}, _, _,_) -> +init_private_key(_,{rsa, PrivateKey}, _, _,_) -> public_key:der_decode('RSAPrivateKey', PrivateKey); -init_private_key({dsa, PrivateKey},_,_,_) -> +init_private_key(_,{dsa, PrivateKey},_,_,_) -> public_key:der_decode('DSAPrivateKey', PrivateKey). -spec(handle_file_error(_,_,_,_,_,_) -> no_return()). @@ -1128,15 +1131,15 @@ file_error(Line, Error, Reason, File, Throw, Stack) -> error_logger:error_report(Report), throw(Throw). -init_diffie_hellman(Params, _,_) when is_binary(Params)-> +init_diffie_hellman(_,Params, _,_) when is_binary(Params)-> public_key:der_decode('DHParameter', Params); -init_diffie_hellman(_,_, client) -> +init_diffie_hellman(_,_,_, client) -> undefined; -init_diffie_hellman(_,undefined, _) -> +init_diffie_hellman(_,_,undefined, _) -> ?DEFAULT_DIFFIE_HELLMAN_PARAMS; -init_diffie_hellman(_, DHParamFile, server) -> +init_diffie_hellman(DbHandle,_, DHParamFile, server) -> try - {ok, List} = ssl_manager:cache_pem_file(DHParamFile), + {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle), case [Entry || Entry = {'DHParameter', _ , _} <- List] of [Entry] -> public_key:pem_entry_decode(Entry); @@ -1180,11 +1183,12 @@ certify_client(#state{client_certificate_requested = true, role = client, connection_states = ConnectionStates0, transport_cb = Transport, negotiated_version = Version, + cert_db = CertDbHandle, cert_db_ref = CertDbRef, session = #session{own_certificate = OwnCert}, socket = Socket, tls_handshake_hashes = Hashes0} = State) -> - Certificate = ssl_handshake:certificate(OwnCert, CertDbRef, client), + Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), {BinCert, ConnectionStates1, Hashes1} = encode_handshake(Certificate, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinCert), @@ -1365,9 +1369,10 @@ certify_server(#state{transport_cb = Transport, negotiated_version = Version, connection_states = ConnectionStates, tls_handshake_hashes = Hashes, + cert_db = CertDbHandle, cert_db_ref = CertDbRef, session = #session{own_certificate = OwnCert}} = State) -> - case ssl_handshake:certificate(OwnCert, CertDbRef, server) of + case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of CertMsg = #certificate{} -> {BinCertMsg, NewConnectionStates, NewHashes} = encode_handshake(CertMsg, Version, ConnectionStates, Hashes), @@ -1454,12 +1459,13 @@ rsa_key_exchange(_, _) -> request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, connection_states = ConnectionStates0, + cert_db = CertDbHandle, cert_db_ref = CertDbRef, tls_handshake_hashes = Hashes0, negotiated_version = Version, socket = Socket, transport_cb = Transport} = State) -> - Msg = ssl_handshake:certificate_request(ConnectionStates0, CertDbRef), + Msg = ssl_handshake:certificate_request(ConnectionStates0, CertDbHandle, CertDbRef), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Msg, Version, ConnectionStates0, Hashes0), Transport:send(Socket, BinMsg), @@ -2040,31 +2046,67 @@ get_socket_opts(Socket, [active | Tags], SockOpts, Acc) -> get_socket_opts(Socket, Tags, SockOpts, [{active, SockOpts#socket_options.active} | Acc]); get_socket_opts(Socket, [Tag | Tags], SockOpts, Acc) -> - case inet:getopts(Socket, [Tag]) of + try inet:getopts(Socket, [Tag]) of {ok, [Opt]} -> get_socket_opts(Socket, Tags, SockOpts, [Opt | Acc]); {error, Error} -> - {error, Error} - end. + {error, {eoptions, {inet_option, Tag, Error}}} + catch + %% So that inet behavior does not crash our process + _:Error -> {error, {eoptions, {inet_option, Tag, Error}}} + end; +get_socket_opts(_,Opts, _,_) -> + {error, {eoptions, {inet_option, Opts, function_clause}}}. set_socket_opts(_, [], SockOpts, []) -> - SockOpts; + {ok, SockOpts}; set_socket_opts(Socket, [], SockOpts, Other) -> %% Set non emulated options - inet:setopts(Socket, Other), - SockOpts; -set_socket_opts(Socket, [{mode, Mode}| Opts], SockOpts, Other) -> + try inet:setopts(Socket, Other) of + ok -> + {ok, SockOpts}; + {error, InetError} -> + {{error, {eoptions, {inet_options, Other, InetError}}}, SockOpts} + catch + _:Error -> + %% So that inet behavior does not crash our process + {{error, {eoptions, {inet_options, Other, Error}}}, SockOpts} + end; + +set_socket_opts(Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary -> set_socket_opts(Socket, Opts, SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(Socket, [{packet, Packet}| Opts], SockOpts, Other) -> +set_socket_opts(_, [{mode, _} = Opt| _], SockOpts, _) -> + {{error, {eoptions, {inet_opt, Opt}}}, SockOpts}; +set_socket_opts(Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw; + Packet == 0; + Packet == 1; + Packet == 2; + Packet == 4; + Packet == asn1; + Packet == cdr; + Packet == sunrm; + Packet == fcgi; + Packet == tpkt; + Packet == line; + Packet == http; + Packet == http_bin -> set_socket_opts(Socket, Opts, SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(Socket, [{header, Header}| Opts], SockOpts, Other) -> +set_socket_opts(_, [{packet, _} = Opt| _], SockOpts, _) -> + {{error, {eoptions, {inet_opt, Opt}}}, SockOpts}; +set_socket_opts(Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) -> set_socket_opts(Socket, Opts, SockOpts#socket_options{header = Header}, Other); -set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) -> +set_socket_opts(_, [{header, _} = Opt| _], SockOpts, _) -> + {{error,{eoptions, {inet_opt, Opt}}}, SockOpts}; +set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once; + Active == true; + Active == false -> set_socket_opts(Socket, Opts, SockOpts#socket_options{active = Active}, Other); +set_socket_opts(_, [{active, _} = Opt| _], SockOpts, _) -> + {{error, {eoptions, {inet_opt, Opt}} }, SockOpts}; set_socket_opts(Socket, [Opt | Opts], SockOpts, Other) -> set_socket_opts(Socket, Opts, SockOpts, [Opt | Other]). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 1f4c44d115..4e74aec4ac 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -31,9 +31,9 @@ -include_lib("public_key/include/public_key.hrl"). -export([master_secret/4, client_hello/6, server_hello/4, hello/4, - hello_request/0, certify/6, certificate/3, + hello_request/0, certify/7, certificate/4, client_certificate_verify/5, certificate_verify/5, - certificate_request/2, key_exchange/2, server_key_exchange_hash/2, + certificate_request/3, key_exchange/2, server_key_exchange_hash/2, finished/4, verify_connection/5, get_tls_handshake/2, decode_client_key/3, server_hello_done/0, encode_handshake/2, init_hashes/0, update_hashes/2, @@ -106,7 +106,7 @@ hello_request() -> %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, - #connection_states{} | {port_num(), #session{}, cache_ref(), + #connection_states{} | {port_num(), #session{}, db_handle(), atom(), #connection_states{}, binary()}, boolean()) -> {tls_version(), session_id(), #connection_states{}}| {tls_version(), {resumed | new, #session{}}, @@ -173,13 +173,13 @@ hello(#client_hello{client_version = ClientVersion, random = Random, end. %%-------------------------------------------------------------------- --spec certify(#certificate{}, term(), integer() | nolimit, +-spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, verify_peer | verify_none, {fun(), term}, client | server) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- -certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef, +certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, MaxPathLen, _Verify, VerifyFunAndState, Role) -> [PeerCert | _] = ASN1Certs, @@ -208,7 +208,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef, end, {TrustedErlCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef), + ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef), case public_key:pkix_path_validation(TrustedErlCert, CertPath, @@ -222,13 +222,13 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef, end. %%-------------------------------------------------------------------- --spec certificate(der_cert(), term(), client | server) -> #certificate{} | #alert{}. +-spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. %% %% Description: Creates a certificate message. %%-------------------------------------------------------------------- -certificate(OwnCert, CertDbRef, client) -> +certificate(OwnCert, CertDbHandle, CertDbRef, client) -> Chain = - case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, CertChain} -> CertChain; {error, _} -> @@ -239,8 +239,8 @@ certificate(OwnCert, CertDbRef, client) -> end, #certificate{asn1_certificates = Chain}; -certificate(OwnCert, CertDbRef, server) -> - case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of +certificate(OwnCert, CertDbHandle, CertDbRef, server) -> + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, Chain} -> #certificate{asn1_certificates = Chain}; {error, _} -> @@ -302,17 +302,17 @@ certificate_verify(Signature, {?'id-dsa' = Algorithm, PublicKey, PublicKeyParams %%-------------------------------------------------------------------- --spec certificate_request(#connection_states{}, certdb_ref()) -> +-spec certificate_request(#connection_states{}, db_handle(), certdb_ref()) -> #certificate_request{}. %% %% Description: Creates a certificate_request message, called by the server. %%-------------------------------------------------------------------- -certificate_request(ConnectionStates, CertDbRef) -> +certificate_request(ConnectionStates, CertDbHandle, CertDbRef) -> #connection_state{security_parameters = #security_parameters{cipher_suite = CipherSuite}} = ssl_record:pending_connection_state(ConnectionStates, read), Types = certificate_types(CipherSuite), - Authorities = certificate_authorities(CertDbRef), + Authorities = certificate_authorities(CertDbHandle, CertDbRef), #certificate_request{ certificate_types = Types, certificate_authorities = Authorities @@ -1071,8 +1071,8 @@ certificate_types({KeyExchange, _, _, _}) certificate_types(_) -> <<?BYTE(?RSA_SIGN)>>. -certificate_authorities(CertDbRef) -> - Authorities = certificate_authorities_from_db(CertDbRef), +certificate_authorities(CertDbHandle, CertDbRef) -> + Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> OTPSubj = TBSCert#'OTPTBSCertificate'.subject, DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), @@ -1084,18 +1084,18 @@ certificate_authorities(CertDbRef) -> end, list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). -certificate_authorities_from_db(CertDbRef) -> - certificate_authorities_from_db(CertDbRef, no_candidate, []). +certificate_authorities_from_db(CertDbHandle, CertDbRef) -> + certificate_authorities_from_db(CertDbHandle, CertDbRef, no_candidate, []). -certificate_authorities_from_db(CertDbRef, PrevKey, Acc) -> - case ssl_manager:issuer_candidate(PrevKey) of +certificate_authorities_from_db(CertDbHandle,CertDbRef, PrevKey, Acc) -> + case ssl_manager:issuer_candidate(PrevKey, CertDbHandle) of no_more_candidates -> lists:reverse(Acc); {{CertDbRef, _, _} = Key, Cert} -> - certificate_authorities_from_db(CertDbRef, Key, [Cert|Acc]); + certificate_authorities_from_db(CertDbHandle, CertDbRef, Key, [Cert|Acc]); {Key, _Cert} -> %% skip certs not from this ssl connection - certificate_authorities_from_db(CertDbRef, Key, Acc) + certificate_authorities_from_db(CertDbHandle, CertDbRef, Key, Acc) end. digitally_signed(Hash, #'RSAPrivateKey'{} = Key) -> diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index c28daa271e..cc66246068 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -33,8 +33,8 @@ -type session_id() :: 0 | binary(). -type tls_version() :: {integer(), integer()}. -type tls_atom_version() :: sslv3 | tlsv1. --type cache_ref() :: term(). --type certdb_ref() :: term(). +-type certdb_ref() :: reference(). +-type db_handle() :: term(). -type key_algo() :: null | rsa | dhe_rsa | dhe_dss | dh_anon. -type der_cert() :: binary(). -type private_key() :: #'RSAPrivateKey'{} | #'DSAPrivateKey'{}. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 1bbb03bdde..b02815bfd8 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -28,8 +28,8 @@ %% Internal application API -export([start_link/1, - connection_init/2, cache_pem_file/1, - lookup_trusted_cert/3, issuer_candidate/1, client_session_id/4, + connection_init/2, cache_pem_file/2, + lookup_trusted_cert/4, issuer_candidate/2, client_session_id/4, server_session_id/4, register_session/2, register_session/3, invalidate_session/2, invalidate_session/3]). @@ -50,7 +50,8 @@ session_cache_cb, session_lifetime, certificate_db, - session_validation_timer + session_validation_timer, + last_delay_timer %% Keep for testing purposes }). -define('24H_in_msec', 8640000). @@ -72,45 +73,45 @@ start_link(Opts) -> %%-------------------------------------------------------------------- -spec connection_init(string()| {der, list()}, client | server) -> - {ok, reference(), cache_ref()}. + {ok, certdb_ref(), db_handle(), db_handle()}. %% %% Description: Do necessary initializations for a new connection. %%-------------------------------------------------------------------- connection_init(Trustedcerts, Role) -> call({connection_init, Trustedcerts, Role}). %%-------------------------------------------------------------------- --spec cache_pem_file(string()) -> {ok, term()} | {error, reason()}. +-spec cache_pem_file(string(), term()) -> {ok, term()} | {error, reason()}. %% %% Description: Cach a pem file and return its content. %%-------------------------------------------------------------------- -cache_pem_file(File) -> +cache_pem_file(File, DbHandle) -> try file:read_file_info(File) of {ok, #file_info{mtime = LastWrite}} -> - cache_pem_file(File, LastWrite) + cache_pem_file(File, LastWrite, DbHandle) catch _:Reason -> {error, Reason} end. %%-------------------------------------------------------------------- --spec lookup_trusted_cert(reference(), serialnumber(), issuer()) -> +-spec lookup_trusted_cert(term(), reference(), serialnumber(), issuer()) -> undefined | {ok, {der_cert(), #'OTPCertificate'{}}}. %% %% Description: Lookup the trusted cert with Key = {reference(), %% serialnumber(), issuer()}. %% -------------------------------------------------------------------- -lookup_trusted_cert(Ref, SerialNumber, Issuer) -> - ssl_certificate_db:lookup_trusted_cert(Ref, SerialNumber, Issuer). +lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> + ssl_certificate_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). %%-------------------------------------------------------------------- --spec issuer_candidate(cert_key() | no_candidate) -> +-spec issuer_candidate(cert_key() | no_candidate, term()) -> {cert_key(), {der_cert(), #'OTPCertificate'{}}} | no_more_candidates. %% %% Description: Return next issuer candidate. %%-------------------------------------------------------------------- -issuer_candidate(PrevCandidateKey) -> - ssl_certificate_db:issuer_candidate(PrevCandidateKey). +issuer_candidate(PrevCandidateKey, DbHandle) -> + ssl_certificate_db:issuer_candidate(PrevCandidateKey, DbHandle). %%-------------------------------------------------------------------- -spec client_session_id(host(), port_num(), #ssl_options{}, der_cert() | undefined) -> session_id(). @@ -192,19 +193,20 @@ init([Opts]) -> %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({{connection_init, "", _Role}, Pid}, _From, - #state{session_cache = Cache} = State) -> + #state{certificate_db = [CertDb |_], + session_cache = Cache} = State) -> erlang:monitor(process, Pid), - Result = {ok, make_ref(), Cache}, + Result = {ok, make_ref(),CertDb, Cache}, {reply, Result, State}; handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, - #state{certificate_db = Db, + #state{certificate_db = [CertDb|_] =Db, session_cache = Cache} = State) -> erlang:monitor(process, Pid), Result = try {ok, Ref} = ssl_certificate_db:add_trusted_certs(Pid, Trustedcerts, Db), - {ok, Ref, Cache} + {ok, Ref, CertDb, Cache} catch _:Reason -> {error, Reason} @@ -265,20 +267,25 @@ handle_cast({register_session, Port, Session}, CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession), {noreply, State}; -handle_cast({invalidate_session, Host, Port, +%%% When a session is invalidated we need to wait a while before deleting +%%% it as there might be pending connections that rightfully needs to look +%%% up the session data but new connections should not get to use this session. +handle_cast({invalidate_session, Host, Port, #session{session_id = ID} = Session}, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> CacheCb:update(Cache, {{Host, Port}, ID}, Session#session{is_resumable = false}), - timer:apply_after(?CLEAN_SESSION_DB, CacheCb, delete, [{{Host, Port}, ID}]), - {noreply, State}; + TRef = + erlang:send_after(delay_time(), self(), {delayed_clean_session, {{Host, Port}, ID}}), + {noreply, State#state{last_delay_timer = TRef}}; handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, #state{session_cache = Cache, session_cache_cb = CacheCb} = State) -> CacheCb:update(Cache, {Port, ID}, Session#session{is_resumable = false}), - timer:apply_after(?CLEAN_SESSION_DB, CacheCb, delete, [{Port, ID}]), - {noreply, State}; + TRef = + erlang:send_after(delay_time(), self(), {delayed_clean_session, {Port, ID}}), + {noreply, State#state{last_delay_timer = TRef}}; handle_cast({recache_pem, File, LastWrite, Pid, From}, #state{certificate_db = [_, FileToRefDb, _]} = State0) -> @@ -312,6 +319,12 @@ handle_info(validate_sessions, #state{session_cache_cb = CacheCb, start_session_validator(Cache, CacheCb, LifeTime), {noreply, State#state{session_validation_timer = Timer}}; +handle_info({delayed_clean_session, Key}, #state{session_cache = Cache, + session_cache_cb = CacheCb + } = State) -> + CacheCb:delete(Cache, Key), + {noreply, State}; + handle_info({'EXIT', _, _}, State) -> %% Session validator died!! Do we need to take any action? %% maybe error log @@ -399,8 +412,8 @@ session_validation({{Port, _}, Session}, LifeTime) -> validate_session(Port, Session, LifeTime), LifeTime. -cache_pem_file(File, LastWrite) -> - case ssl_certificate_db:lookup_cached_certs(File) of +cache_pem_file(File, LastWrite, DbHandle) -> + case ssl_certificate_db:lookup_cached_certs(DbHandle,File) of [{_, {Mtime, Content}}] -> case LastWrite of Mtime -> @@ -411,3 +424,11 @@ cache_pem_file(File, LastWrite) -> [] -> call({cache_pem, File, LastWrite}) end. + +delay_time() -> + case application:get_env(ssl, session_delay_cleanup_time) of + {ok, Time} when is_integer(Time) -> + Time; + _ -> + ?CLEAN_SESSION_DB + end. diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index f1c0073965..72091fdd5f 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -62,6 +62,8 @@ -compile(inline). +-define(INITIAL_BYTES, 5). + %%==================================================================== %% Internal application API %%==================================================================== @@ -340,7 +342,7 @@ get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, version = {MajVer, MinVer}, fragment = Data} | Acc]); -%% Matches a ssl v2 client hello message. +%% Matches an ssl v2 client hello message. %% The server must be able to receive such messages, from clients that %% are willing to use ssl v3 or higher, but have ssl v2 compatibility. get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>, @@ -360,16 +362,20 @@ get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>, get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH-> + _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); get_tls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) - when Length0 > ?MAX_CIPHER_TEXT_LENGTH-> + when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); get_tls_records_aux(Data, Acc) -> - {lists:reverse(Acc), Data}. - + case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of + true -> + {lists:reverse(Acc), Data}; + false -> + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + end. %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> tls_version() | tls_atom_version(). diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index dc4b7a711c..85c9fcb61c 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -48,7 +48,7 @@ is_new(_ClientSuggestion, _ServerDecision) -> true. %%-------------------------------------------------------------------- --spec id({host(), port_num(), #ssl_options{}}, cache_ref(), atom(), +-spec id({host(), port_num(), #ssl_options{}}, db_handle(), atom(), undefined | binary()) -> binary(). %% %% Description: Should be called by the client side to get an id @@ -63,7 +63,7 @@ id(ClientInfo, Cache, CacheCb, OwnCert) -> end. %%-------------------------------------------------------------------- --spec id(port_num(), binary(), #ssl_options{}, cache_ref(), +-spec id(port_num(), binary(), #ssl_options{}, db_handle(), atom(), seconds(), binary()) -> binary(). %% %% Description: Should be called by the server side to get an id diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index 823bf7acfa..66610817be 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. 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 @@ -31,7 +31,7 @@ -type key() :: {{host(), port_num()}, session_id()} | {port_num(), session_id()}. %%-------------------------------------------------------------------- --spec init(list()) -> cache_ref(). %% Returns reference to the cache (opaque) +-spec init(list()) -> db_handle(). %% Returns reference to the cache (opaque) %% %% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- @@ -39,7 +39,7 @@ init(_) -> ets:new(cache_name(), [set, protected]). %%-------------------------------------------------------------------- --spec terminate(cache_ref()) -> any(). %% +-spec terminate(db_handle()) -> any(). %% %% Description: Handles cache table at termination of ssl manager. %%-------------------------------------------------------------------- @@ -47,7 +47,7 @@ terminate(Cache) -> ets:delete(Cache). %%-------------------------------------------------------------------- --spec lookup(cache_ref(), key()) -> #session{} | undefined. +-spec lookup(db_handle(), key()) -> #session{} | undefined. %% %% Description: Looks up a cach entry. Should be callable from any %% process. @@ -61,7 +61,7 @@ lookup(Cache, Key) -> end. %%-------------------------------------------------------------------- --spec update(cache_ref(), key(), #session{}) -> any(). +-spec update(db_handle(), key(), #session{}) -> any(). %% %% Description: Caches a new session or updates a already cached one. %% Will only be called from the ssl_manager process. @@ -70,7 +70,7 @@ update(Cache, Key, Session) -> ets:insert(Cache, {Key, Session}). %%-------------------------------------------------------------------- --spec delete(cache_ref(), key()) -> any(). +-spec delete(db_handle(), key()) -> any(). %% %% Description: Delets a cache entry. %% Will only be called from the ssl_manager process. @@ -79,7 +79,7 @@ delete(Cache, Key) -> ets:delete(Cache, Key). %%-------------------------------------------------------------------- --spec foldl(fun(), term(), cache_ref()) -> term(). +-spec foldl(fun(), term(), db_handle()) -> term(). %% %% Description: Calls Fun(Elem, AccIn) on successive elements of the %% cache, starting with AccIn == Acc0. Fun/2 must return a new @@ -91,7 +91,7 @@ foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). %%-------------------------------------------------------------------- --spec select_session(cache_ref(), {host(), port_num()} | port_num()) -> [#session{}]. +-spec select_session(db_handle(), {host(), port_num()} | port_num()) -> [#session{}]. %% %% Description: Selects a session that could be reused. Should be callable %% from any process. diff --git a/lib/ssl/src/ssl_ssl2.erl b/lib/ssl/src/ssl_ssl2.erl index b1005b1acb..30a3a5fc98 100644 --- a/lib/ssl/src/ssl_ssl2.erl +++ b/lib/ssl/src/ssl_ssl2.erl @@ -20,7 +20,7 @@ %% %%---------------------------------------------------------------------- %% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher -%% will send a sslv2 hello. +%% will send an sslv2 hello. %%---------------------------------------------------------------------- -module(ssl_ssl2). diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 53b2223035..5be07cad2c 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -61,8 +61,10 @@ HRL_FILES = ssl_test_MACHINE.hrl HRL_FILES_SRC = \ ssl_int.hrl \ + ssl_internal.hrl\ ssl_alert.hrl \ - ssl_handshake.hrl + ssl_handshake.hrl \ + ssl_record.hrl HRL_FILES_INC = diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 4f0907027f..37a021e7cf 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -25,11 +25,12 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). --include("test_server_line.hrl"). -include_lib("public_key/include/public_key.hrl"). -include("ssl_alert.hrl"). -include("ssl_int.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). -define('24H_in_sec', 86400). -define(TIMEOUT, 60000). @@ -208,8 +209,12 @@ all() -> empty_protocol_versions, controlling_process, controller_dies, client_closes_socket, peercert, connect_dist, peername, sockname, socket_options, + invalid_inet_get_option, invalid_inet_get_option_not_list, + invalid_inet_get_option_improper_list, + invalid_inet_set_option, invalid_inet_set_option_not_list, + invalid_inet_set_option_improper_list, misc_ssl_options, versions, cipher_suites, upgrade, - upgrade_with_timeout, tcp_connect, ipv6, ekeyfile, + upgrade_with_timeout, tcp_connect, tcp_connect_big, ipv6, ekeyfile, ecertfile, ecacertfile, eoptions, shutdown, shutdown_write, shutdown_both, shutdown_error, ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_ssl3, @@ -252,7 +257,7 @@ all() -> %%different_ca_peer_sign, no_reuses_session_server_restart_new_cert, no_reuses_session_server_restart_new_cert_file, reuseaddr, - hibernate + hibernate, connect_twice ]. groups() -> @@ -808,8 +813,218 @@ socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), ssl:setopts(Socket, [{nodelay, true}]), {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), + {ok, All} = ssl:getopts(Socket, []), + test_server:format("All opts ~p~n", [All]), ok. + + +%%-------------------------------------------------------------------- +invalid_inet_get_option(doc) -> + ["Test handling of invalid inet options in getopts"]; + +invalid_inet_get_option(suite) -> + []; + +invalid_inet_get_option(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +get_invalid_inet_option(Socket) -> + {error, {eoptions, {inet_option, foo, _}}} = ssl:getopts(Socket, [foo]), + ok. + +%%-------------------------------------------------------------------- +invalid_inet_get_option_not_list(doc) -> + ["Test handling of invalid type in getopts"]; + +invalid_inet_get_option_not_list(suite) -> + []; + +invalid_inet_get_option_not_list(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +get_invalid_inet_option_not_list(Socket) -> + {error, {eoptions, {inet_options, some_invalid_atom_here}}} + = ssl:getopts(Socket, some_invalid_atom_here), + ok. + +%%-------------------------------------------------------------------- +invalid_inet_get_option_improper_list(doc) -> + ["Test handling of invalid type in getopts"]; + +invalid_inet_get_option_improper_list(suite) -> + []; + +invalid_inet_get_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +get_invalid_inet_option_improper_list(Socket) -> + {error, {eoptions, {inet_option, foo,_}}} = ssl:getopts(Socket, [packet | foo]), + ok. + +%%-------------------------------------------------------------------- +invalid_inet_set_option(doc) -> + ["Test handling of invalid inet options in setopts"]; + +invalid_inet_set_option(suite) -> + []; + +invalid_inet_set_option(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +set_invalid_inet_option(Socket) -> + {error, {eoptions, {inet_opt, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), + {error, {eoptions, {inet_opt, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), + {error, {eoptions, {inet_opt, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), + {error, {eoptions, {inet_opt, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), + ok. +%%-------------------------------------------------------------------- +invalid_inet_set_option_not_list(doc) -> + ["Test handling of invalid type in setopts"]; + +invalid_inet_set_option_not_list(suite) -> + []; + +invalid_inet_set_option_not_list(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +set_invalid_inet_option_not_list(Socket) -> + {error, {eoptions, {not_a_proplist, some_invalid_atom_here}}} + = ssl:setopts(Socket, some_invalid_atom_here), + ok. + +%%-------------------------------------------------------------------- +invalid_inet_set_option_improper_list(doc) -> + ["Test handling of invalid tye in setopts"]; + +invalid_inet_set_option_improper_list(suite) -> + []; + +invalid_inet_set_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +set_invalid_inet_option_improper_list(Socket) -> + {error, {eoptions, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = + ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), + ok. + %%-------------------------------------------------------------------- misc_ssl_options(doc) -> ["Test what happens when we give valid options"]; @@ -1097,6 +1312,41 @@ tcp_connect(Config) when is_list(Config) -> end end. +tcp_connect_big(doc) -> + ["Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"]; + +tcp_connect_big(suite) -> + []; + +tcp_connect_big(Config) when is_list(Config) -> + ServerOpts = ?config(server_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + test_server:format("Testcase ~p connected to Server ~p ~n", [self(), Server]), + + Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + gen_tcp:send(Socket, <<?BYTE(0), + ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, timeout}} -> + test_server:fail("hangs"); + {Server, {error, Error}} -> + test_server:format("Error ~p", [Error]) + end + end. dummy(_Socket) -> %% Should not happen as the ssl connection will not be established @@ -1659,7 +1909,7 @@ reuse_session(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client0 = @@ -1681,7 +1931,7 @@ reuse_session(Config) when is_list(Config) -> Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client1, SessionInfo} -> @@ -1697,7 +1947,7 @@ reuse_session(Config) when is_list(Config) -> Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{reuse_sessions, false} | ClientOpts]}]), receive @@ -1713,7 +1963,7 @@ reuse_session(Config) when is_list(Config) -> Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, [{reuse_sessions, false} | ServerOpts]}]), Port1 = ssl_test_lib:inet_port(Server1), @@ -1737,7 +1987,7 @@ reuse_session(Config) when is_list(Config) -> Client4 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port1}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive @@ -1756,9 +2006,6 @@ reuse_session(Config) when is_list(Config) -> ssl_test_lib:close(Client3), ssl_test_lib:close(Client4). -session_info_result(Socket) -> - ssl:session_info(Socket). - %%-------------------------------------------------------------------- reuse_session_expired(doc) -> ["Test sessions is not reused when it has expired"]; @@ -1774,7 +2021,7 @@ reuse_session_expired(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client0 = @@ -1796,7 +2043,7 @@ reuse_session_expired(Config) when is_list(Config) -> Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client1, SessionInfo} -> @@ -1815,7 +2062,7 @@ reuse_session_expired(Config) when is_list(Config) -> Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client2, SessionInfo} -> @@ -1844,7 +2091,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, [{reuse_session, fun(_,_,_,_) -> false end} | @@ -1870,7 +2117,7 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client1, SessionInfo} -> @@ -3179,7 +3426,7 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client0 = @@ -3207,7 +3454,7 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client1, SessionInfo} -> @@ -3238,7 +3485,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {options, NewServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client0 = @@ -3268,7 +3515,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {mfa, {?MODULE, session_info_result, []}}, + {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, ClientOpts}]), receive {Client1, SessionInfo} -> @@ -3304,6 +3551,7 @@ reuseaddr(Config) when is_list(Config) -> {options, [{active, false} | ClientOpts]}]), test_server:sleep(?SLEEP), ssl_test_lib:close(Server), + ssl_test_lib:close(Client), Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, @@ -3361,6 +3609,54 @@ hibernate(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- + +connect_twice(doc) -> + [""]; +connect_twice(suite) -> + []; +connect_twice(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), + Server ! listen, + + {Client1, #sslsocket{}} = + ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), + + test_server:format("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:check_result(Server, ok, Client1, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ssl_test_lib:close(Client1). + + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- send_recv_result(Socket) -> @@ -3438,7 +3734,7 @@ session_cache_process_mnesia(suite) -> session_cache_process_mnesia(Config) when is_list(Config) -> session_cache_process(mnesia,Config). -session_cache_process(Type,Config) when is_list(Config) -> +session_cache_process(_Type,Config) when is_list(Config) -> reuse_session(Config). init([Type]) -> diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index a43b9ab586..5ea45018e6 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -26,9 +26,11 @@ -include_lib("common_test/include/ct.hrl"). +-define(DELAY, 500). -define(SLEEP, 500). -define(TIMEOUT, 60000). -define(LONG_TIMEOUT, 600000). + -behaviour(ssl_session_cache_api). %% For the session cache tests @@ -95,6 +97,16 @@ init_per_testcase(session_cache_process_mnesia, Config) -> mnesia:start(), init_customized_session_cache(mnesia, Config); +init_per_testcase(session_cleanup, Config0) -> + Config = lists:keydelete(watchdog, 1, Config0), + Dog = test_server:timetrap(?TIMEOUT), + ssl:stop(), + application:load(ssl), + application:set_env(ssl, session_lifetime, 5), + application:set_env(ssl, session_delay_cleanup_time, ?DELAY), + ssl:start(), + [{watchdog, Dog} | Config]; + init_per_testcase(_TestCase, Config0) -> Config = lists:keydelete(watchdog, 1, Config0), Dog = test_server:timetrap(?TIMEOUT), @@ -128,6 +140,10 @@ end_per_testcase(session_cache_process_mnesia, Config) -> ssl:stop(), ssl:start(), end_per_testcase(default_action, Config); +end_per_testcase(session_cleanup, Config) -> + application:unset_env(ssl, session_delay_cleanup_time), + application:unset_env(ssl, session_lifetime), + end_per_testcase(default_action, Config); end_per_testcase(_TestCase, Config) -> Dog = ?config(watchdog, Config), case Dog of @@ -148,7 +164,8 @@ end_per_testcase(_TestCase, Config) -> suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [session_cache_process_list, + [session_cleanup, + session_cache_process_list, session_cache_process_mnesia]. groups() -> @@ -159,7 +176,95 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +%%-------------------------------------------------------------------- +session_cleanup(doc) -> + ["Test that sessions are cleand up eventually, so that the session table " + "does not grow and grow ..."]; +session_cleanup(suite) -> + []; +session_cleanup(Config)when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered + test_server:sleep(?SLEEP), + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = state(Prop), + Cache = element(2, State), + SessionTimer = element(6, State), + + Id = proplists:get_value(session_id, SessionInfo), + CSession = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), + SSession = ssl_session_cache:lookup(Cache, {Port, Id}), + + true = CSession =/= undefined, + true = SSession =/= undefined, + + %% Make sure session has expired and been cleaned up + check_timer(SessionTimer), + test_server:sleep(?DELAY *2), %% Delay time + some extra time + + DelayTimer = get_delay_timer(), + + check_timer(DelayTimer), + + test_server:sleep(?SLEEP), %% Make sure clean has had to run + + undefined = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), + undefined = ssl_session_cache:lookup(Cache, {Port, Id}), + + process_flag(trap_exit, false), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +state([{data,[{"State", State}]} | _]) -> + State; +state([_ | Rest]) -> + state(Rest). + +check_timer(Timer) -> + case erlang:read_timer(Timer) of + false -> + {status, _, _, _} = sys:get_status(whereis(ssl_manager)), + ok; + Int -> + test_server:sleep(Int), + check_timer(Timer) + end. +get_delay_timer() -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = state(Prop), + case element(7, State) of + undefined -> + test_server:sleep(?SLEEP), + get_delay_timer(); + DelayTimer -> + DelayTimer + end. +%%-------------------------------------------------------------------- session_cache_process_list(doc) -> ["Test reuse of sessions (short handshake)"]; @@ -176,7 +281,6 @@ session_cache_process_mnesia(suite) -> session_cache_process_mnesia(Config) when is_list(Config) -> session_cache_process(mnesia,Config). - %%-------------------------------------------------------------------- %%% Session cache API callbacks %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 40bbdf1dbd..b7916b96eb 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -670,3 +670,6 @@ cipher_result(Socket, Result) -> Other -> {unexpected, Other} end. + +session_info_result(Socket) -> + ssl:session_info(Socket). diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 0e80e42637..8286201df4 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 4.1.5 +SSL_VSN = 4.1.6 diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml index 4876b37127..f8db48e00c 100644 --- a/lib/stdlib/doc/src/calendar.xml +++ b/lib/stdlib/doc/src/calendar.xml @@ -75,13 +75,13 @@ <datatypes> <datatype> - <name name="t_datetime"/> + <name name="datetime"/> </datatype> <datatype> - <name name="t_datetime1970"/> + <name name="datetime1970"/> </datatype> <datatype> - <name name="t_date"/> + <name name="date"/> </datatype> <datatype> <name name="year"/> @@ -100,7 +100,7 @@ <name name="day"/> </datatype> <datatype> - <name name="t_time"/> + <name name="time"/> </datatype> <datatype> <name name="hour"/> @@ -118,12 +118,7 @@ <name name="ldom"/> </datatype> <datatype> - <name name="t_now"/> - <desc><p>See <seealso marker="erts:erlang#now/0">erlang:now/0</seealso>.</p> - </desc> - </datatype> - <datatype> - <name name="t_yearweeknum"/> + <name name="yearweeknum"/> </datatype> <datatype> <name name="weeknum"/> diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml index d15383c621..4900a8f0ef 100644 --- a/lib/stdlib/doc/src/gen_fsm.xml +++ b/lib/stdlib/doc/src/gen_fsm.xml @@ -438,7 +438,7 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 <fsummary>Initialize process and internal state name and state data.</fsummary> <type> <v>Args = term()</v> - <v>Return = {ok,StateName,StateData} | {ok,StateName,StateData,Timeout}</v> + <v>Result = {ok,StateName,StateData} | {ok,StateName,StateData,Timeout}</v> <v> | {ok,StateName,StateData,hibernate}</v> <v> | {stop,Reason} | ignore</v> <v> StateName = atom()</v> diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml index af9c75d546..667d758e29 100644 --- a/lib/stdlib/doc/src/io.xml +++ b/lib/stdlib/doc/src/io.xml @@ -439,7 +439,7 @@ ok</pre> <item> <p>Prints the argument with the <c>string</c> syntax. The argument is, if no Unicode translation modifier is present, an - <seealso marker="erts:erlang#iolist_definition">I/O list</seealso>, a binary, or an atom. If the Unicode translation modifier ('t') is in effect, the argument is unicode:chardata(), meaning that binaries are in UTF-8. The characters + iolist(), a binary, or an atom. If the Unicode translation modifier ('t') is in effect, the argument is unicode:chardata(), meaning that binaries are in UTF-8. The characters are printed without quotes. The string is first truncated by the given precision and then padded and justified to the given field width. The default precision is the field width.</p> diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml index 416df1f02c..b48ad8c1f3 100644 --- a/lib/stdlib/doc/src/unicode_usage.xml +++ b/lib/stdlib/doc/src/unicode_usage.xml @@ -52,7 +52,7 @@ <tag>UCS-4</tag> <item>Basically the same as UTF-32, but without some Unicode semantics, defined by IEEE and has little use as a separate encoding standard. For all normal (and possibly abnormal) usages, UTF-32 and UCS-4 are interchangeable.</item> </taglist> -<p>Certain ranges of characters are left unused and certain ranges are even deemed invalid. The most notable invalid range is 16#D800 - 16#DFFF, as the UTF-16 encoding does not allow for encoding of these numbers. It can be speculated that the UTF-16 encoding standard was, from the beginning, expected to be able to hold all Unicode characters in one 16-bit entity, but then had to be extended, leaving a whole in the Unicode range to cope with backward compatibility.</p> +<p>Certain ranges of characters are left unused and certain ranges are even deemed invalid. The most notable invalid range is 16#D800 - 16#DFFF, as the UTF-16 encoding does not allow for encoding of these numbers. It can be speculated that the UTF-16 encoding standard was, from the beginning, expected to be able to hold all Unicode characters in one 16-bit entity, but then had to be extended, leaving a hole in the Unicode range to cope with backward compatibility.</p> <p>Additionally, the codepoint 16#FEFF is used for byte order marks (BOM's) and use of that character is not encouraged in other contexts than that. It actually is valid though, as the character "ZWNBS" (Zero Width Non Breaking Space). BOM's are used to identify encodings and byte order for programs where such parameters are not known in advance. Byte order marks are more seldom used than one could expect, put their use is becoming more widely spread as they provide the means for programs to make educated guesses about the Unicode format of a certain file.</p> </section> <section> diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl index 8d1071209e..0320e0cd0e 100644 --- a/lib/stdlib/src/calendar.erl +++ b/lib/stdlib/src/calendar.erl @@ -63,7 +63,7 @@ %% Types %%---------------------------------------------------------------------- --export_type([t_now/0]). +-export_type([date/0, time/0, datetime/0, datetime1970/0]). -type year() :: non_neg_integer(). -type year1970() :: 1970..10000. % should probably be 1970.. @@ -76,15 +76,11 @@ -type ldom() :: 28 | 29 | 30 | 31. % last day of month -type weeknum() :: 1..53. --type t_now() :: {MegaSecs :: non_neg_integer(), - Secs :: non_neg_integer(), - MicroSecs :: non_neg_integer()}. - --type t_date() :: {year(),month(),day()}. --type t_time() :: {hour(),minute(),second()}. --type t_datetime() :: {t_date(),t_time()}. --type t_datetime1970() :: {{year1970(),month(),day()},t_time()}. --type t_yearweeknum() :: {year(),weeknum()}. +-type date() :: {year(),month(),day()}. +-type time() :: {hour(),minute(),second()}. +-type datetime() :: {date(),time()}. +-type datetime1970() :: {{year1970(),month(),day()},time()}. +-type yearweeknum() :: {year(),weeknum()}. %%---------------------------------------------------------------------- @@ -123,7 +119,7 @@ date_to_gregorian_days(Year, Month, Day) when is_integer(Day), Day > 0 -> end. -spec date_to_gregorian_days(Date) -> Days when - Date :: t_date(), + Date :: date(), Days :: non_neg_integer(). date_to_gregorian_days({Year, Month, Day}) -> date_to_gregorian_days(Year, Month, Day). @@ -135,7 +131,7 @@ date_to_gregorian_days({Year, Month, Day}) -> %% January 1st. %% -spec datetime_to_gregorian_seconds(DateTime) -> Seconds when - DateTime :: t_datetime(), + DateTime :: datetime(), Seconds :: non_neg_integer(). datetime_to_gregorian_seconds({Date, Time}) -> ?SECONDS_PER_DAY*date_to_gregorian_days(Date) + @@ -155,14 +151,14 @@ day_of_the_week(Year, Month, Day) -> (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1. -spec day_of_the_week(Date) -> daynum() when - Date:: t_date(). + Date:: date(). day_of_the_week({Year, Month, Day}) -> day_of_the_week(Year, Month, Day). %% gregorian_days_to_date(Days) = {Year, Month, Day} %% --spec gregorian_days_to_date(Days) -> t_date() when +-spec gregorian_days_to_date(Days) -> date() when Days :: non_neg_integer(). gregorian_days_to_date(Days) -> {Year, DayOfYear} = day_to_year(Days), @@ -172,7 +168,7 @@ gregorian_days_to_date(Days) -> %% gregorian_seconds_to_datetime(Secs) %% --spec gregorian_seconds_to_datetime(Seconds) -> t_datetime() when +-spec gregorian_seconds_to_datetime(Seconds) -> datetime() when Seconds :: non_neg_integer(). gregorian_seconds_to_datetime(Secs) when Secs >= 0 -> Days = Secs div ?SECONDS_PER_DAY, @@ -198,7 +194,7 @@ is_leap_year1(_) -> false. %% %% Calculates the iso week number for the current date. %% --spec iso_week_number() -> t_yearweeknum(). +-spec iso_week_number() -> yearweeknum(). iso_week_number() -> {Date, _} = local_time(), iso_week_number(Date). @@ -207,8 +203,8 @@ iso_week_number() -> %% %% Calculates the iso week number for the given date. %% --spec iso_week_number(Date) -> t_yearweeknum() when - Date :: t_date(). +-spec iso_week_number(Date) -> yearweeknum() when + Date :: date(). iso_week_number({Year, Month, Day}) -> D = date_to_gregorian_days({Year, Month, Day}), W01_1_Year = gregorian_days_of_iso_w01_1(Year), @@ -260,7 +256,7 @@ last_day_of_the_month1(_, M) when is_integer(M), M > 0, M < 13 -> %% local_time() %% %% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. --spec local_time() -> t_datetime(). +-spec local_time() -> datetime(). local_time() -> erlang:localtime(). @@ -268,20 +264,20 @@ local_time() -> %% local_time_to_universal_time(DateTime) %% -spec local_time_to_universal_time(DateTime1) -> DateTime2 when - DateTime1 :: t_datetime1970(), - DateTime2 :: t_datetime1970(). + DateTime1 :: datetime1970(), + DateTime2 :: datetime1970(). local_time_to_universal_time(DateTime) -> erlang:localtime_to_universaltime(DateTime). --spec local_time_to_universal_time(t_datetime1970(), +-spec local_time_to_universal_time(datetime1970(), 'true' | 'false' | 'undefined') -> - t_datetime1970(). + datetime1970(). local_time_to_universal_time(DateTime, IsDst) -> erlang:localtime_to_universaltime(DateTime, IsDst). -spec local_time_to_universal_time_dst(DateTime1) -> [DateTime] when - DateTime1 :: t_datetime1970(), - DateTime :: t_datetime1970(). + DateTime1 :: datetime1970(), + DateTime :: datetime1970(). local_time_to_universal_time_dst(DateTime) -> UtDst = erlang:localtime_to_universaltime(DateTime, true), Ut = erlang:localtime_to_universaltime(DateTime, false), @@ -309,14 +305,14 @@ local_time_to_universal_time_dst(DateTime) -> %% = MilliSec = integer() %% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. %% --spec now_to_datetime(Now) -> t_datetime1970() when - Now :: t_now(). +-spec now_to_datetime(Now) -> datetime1970() when + Now :: erlang:timestamp(). now_to_datetime({MSec, Sec, _uSec}) -> Sec0 = MSec*1000000 + Sec + ?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY, gregorian_seconds_to_datetime(Sec0). --spec now_to_universal_time(Now) -> t_datetime1970() when - Now :: t_now(). +-spec now_to_universal_time(Now) -> datetime1970() when + Now :: erlang:timestamp(). now_to_universal_time(Now) -> now_to_datetime(Now). @@ -325,8 +321,8 @@ now_to_universal_time(Now) -> %% %% Args: Now = now() %% --spec now_to_local_time(Now) -> t_datetime1970() when - Now :: t_now(). +-spec now_to_local_time(Now) -> datetime1970() when + Now :: erlang:timestamp(). now_to_local_time({MSec, Sec, _uSec}) -> erlang:universaltime_to_localtime( now_to_universal_time({MSec, Sec, _uSec})). @@ -338,7 +334,7 @@ now_to_local_time({MSec, Sec, _uSec}) -> -spec seconds_to_daystime(Seconds) -> {Days, Time} when Seconds :: integer(), Days :: integer(), - Time :: t_time(). + Time :: time(). seconds_to_daystime(Secs) -> Days0 = Secs div ?SECONDS_PER_DAY, Secs0 = Secs rem ?SECONDS_PER_DAY, @@ -356,7 +352,7 @@ seconds_to_daystime(Secs) -> %% Wraps. %% -type secs_per_day() :: 0..?SECONDS_PER_DAY. --spec seconds_to_time(Seconds) -> t_time() when +-spec seconds_to_time(Seconds) -> time() when Seconds :: secs_per_day(). seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY -> Secs0 = Secs rem ?SECONDS_PER_DAY, @@ -375,10 +371,10 @@ seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY -> %% Year = Month = Day = Hour = Minute = Sec = integer() %% -spec time_difference(T1, T2) -> {Days, Time} when - T1 :: t_datetime(), - T2 :: t_datetime(), + T1 :: datetime(), + T2 :: datetime(), Days :: integer(), - Time :: t_time(). + Time :: time(). time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}}, {{Y2, Mo2, D2}, {H2, Mi2, S2}}) -> Secs = datetime_to_gregorian_seconds({{Y2, Mo2, D2}, {H2, Mi2, S2}}) - @@ -390,7 +386,7 @@ time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}}, %% time_to_seconds(Time) %% -spec time_to_seconds(Time) -> secs_per_day() when - Time :: t_time(). + Time :: time(). time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) -> H * ?SECONDS_PER_HOUR + M * ?SECONDS_PER_MINUTE + S. @@ -399,15 +395,15 @@ time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) -> %% universal_time() %% %% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. --spec universal_time() -> t_datetime(). +-spec universal_time() -> datetime(). universal_time() -> erlang:universaltime(). %% universal_time_to_local_time(DateTime) %% --spec universal_time_to_local_time(DateTime) -> t_datetime() when - DateTime :: t_datetime1970(). +-spec universal_time_to_local_time(DateTime) -> datetime() when + DateTime :: datetime1970(). universal_time_to_local_time(DateTime) -> erlang:universaltime_to_localtime(DateTime). @@ -429,7 +425,7 @@ valid_date1(_, _, _) -> false. -spec valid_date(Date) -> boolean() when - Date :: t_date(). + Date :: date(). valid_date({Y, M, D}) -> valid_date(Y, M, D). diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl index 718ca2e91a..10b2ed2e49 100644 --- a/lib/stdlib/src/erl_scan.erl +++ b/lib/stdlib/src/erl_scan.erl @@ -408,7 +408,12 @@ set_attr(line, {Line,Column}, Fun) when ?ALINE(Line), ?COLUMN(Column) -> end; set_attr(line=Tag, Attrs, Fun) when is_list(Attrs) -> {line,Line} = lists:keyfind(Tag, 1, Attrs), - lists:keyreplace(Tag, 1, Attrs, {line,Fun(Line)}); + case lists:keyreplace(Tag, 1, Attrs, {line,Fun(Line)}) of + [{line,Ln}] when ?ALINE(Ln) -> + Ln; + As -> + As + end; set_attr(T1, T2, T3) -> erlang:error(badarg, [T1,T2,T3]). diff --git a/lib/stdlib/src/erl_tar.erl b/lib/stdlib/src/erl_tar.erl index fd85c7aef5..306834e845 100644 --- a/lib/stdlib/src/erl_tar.erl +++ b/lib/stdlib/src/erl_tar.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. 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 @@ -798,30 +798,10 @@ set_extracted_file_info(Name, #tar_header{mode=Mode, mtime=Mtime}) -> %% Makes all directories leading up to the file. -make_dirs(Name, Type) -> - make_dirs1(filename:split(Name), Type). - -make_dirs1([Dir, Next|Rest], Type) -> - case file:read_file_info(Dir) of - {ok, #file_info{type=directory}} -> - make_dirs1([filename:join(Dir, Next)|Rest], Type); - {ok, #file_info{}} -> - throw({error, enotdir}); - {error, _} -> - case file:make_dir(Dir) of - ok -> - make_dirs1([filename:join(Dir, Next)|Rest], Type); - {error, Reason} -> - throw({error, Reason}) - end - end; -make_dirs1([_], file) -> ok; -make_dirs1([Dir], dir) -> - file:make_dir(Dir); -make_dirs1([], _) -> - %% There must be something wrong here. The list was not supposed - %% to be empty. - throw({error, enoent}). +make_dirs(Name, file) -> + filelib:ensure_dir(Name); +make_dirs(Name, dir) -> + filelib:ensure_dir(filename:join(Name,"*")). %% Prints the message on if the verbose option is given (for reading). diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl index 54c7283abf..165e03e506 100644 --- a/lib/stdlib/src/io_lib.erl +++ b/lib/stdlib/src/io_lib.erl @@ -100,7 +100,7 @@ fwrite(Format, Args) -> -spec fread(Format, String) -> Result when Format :: string(), String :: string(), - Result :: {'ok', InputList :: chars(), LeftOverChars :: string()} + Result :: {'ok', InputList :: [term()], LeftOverChars :: string()} | {'more', RestFormat :: string(), Nchars :: non_neg_integer(), InputStack :: chars()} @@ -115,7 +115,7 @@ fread(Chars, Format) -> Format :: string(), Return :: {'more', Continuation1 :: continuation()} | {'done', Result, LeftOverChars :: string()}, - Result :: {'ok', InputList :: chars()} + Result :: {'ok', InputList :: [term()]} | 'eof' | {'error', What :: term()}. diff --git a/lib/stdlib/src/io_lib_fread.erl b/lib/stdlib/src/io_lib_fread.erl index 52aa4d073c..ded1346097 100644 --- a/lib/stdlib/src/io_lib_fread.erl +++ b/lib/stdlib/src/io_lib_fread.erl @@ -24,6 +24,10 @@ -import(lists, [reverse/1,reverse/2]). +-define(is_whitespace(C), + ((C) =:= $\s orelse (C) =:= $\t + orelse (C) =:= $\r orelse (C) =:= $\n)). + %%----------------------------------------------------------------------- %% fread(Continuation, CharList, FormatString) @@ -106,31 +110,27 @@ fread_line(Format0, Line, N0, Results0, More, Newline) -> fread(Format, Line) -> fread(Format, Line, 0, []). -fread([$~|Format0], Line, N, Results) -> +fread([$~|Format0]=AllFormat, Line, N, Results) -> {Format,F,Sup,Unicode} = fread_field(Format0), - fread1(Format, F, Sup, Unicode, Line, N, Results, Format0); -fread([$\s|Format], Line, N, Results) -> - fread_skip_white(Format, Line, N, Results); -fread([$\t|Format], Line, N, Results) -> - fread_skip_white(Format, Line, N, Results); -fread([$\r|Format], Line, N, Results) -> - fread_skip_white(Format, Line, N, Results); -fread([$\n|Format], Line, N, Results) -> + fread1(Format, F, Sup, Unicode, Line, N, Results, AllFormat); +fread([C|Format], Line, N, Results) when ?is_whitespace(C) -> fread_skip_white(Format, Line, N, Results); fread([C|Format], [C|Line], N, Results) -> fread(Format, Line, N+1, Results); fread([_F|_Format], [_C|_Line], _N, _Results) -> fread_error(input); +fread([_|_]=Format, [], N, Results) -> + {more,Format,N,Results}; +fread([_|_], eof, 0, []) -> + %% This is at start of input so no error. + eof; +fread([_|_], eof, _N, _Results) -> + %% This is an error as there is no more input. + fread_error(input); fread([], Line, _N, Results) -> {ok,reverse(Results),Line}. -fread_skip_white(Format, [$\s|Line], N, Results) -> - fread_skip_white(Format, Line, N+1, Results); -fread_skip_white(Format, [$\t|Line], N, Results) -> - fread_skip_white(Format, Line, N+1, Results); -fread_skip_white(Format, [$\r|Line], N, Results) -> - fread_skip_white(Format, Line, N+1, Results); -fread_skip_white(Format, [$\n|Line], N, Results) -> +fread_skip_white(Format, [C|Line], N, Results) when ?is_whitespace(C) -> fread_skip_white(Format, Line, N+1, Results); fread_skip_white(Format, Line, N, Results) -> fread(Format, Line, N, Results). @@ -166,9 +166,9 @@ fread1([$l|Format], _F, Sup, _U, Line, N, Res, _AllFormat) -> fread(Format, Line, N, fread_result(Sup, N, Res)); fread1(_Format, _F, _Sup, _U, [], N, Res, AllFormat) -> %% Need more input here. - {more,[$~|AllFormat],N,Res}; -fread1(_Format, _F, _Sup, _U, eof, _N, [], _AllFormat) -> - %% This is at start of format string so no error. + {more,AllFormat,N,Res}; +fread1(_Format, _F, _Sup, _U, eof, 0, [], _AllFormat) -> + %% This is at start of input so no error. eof; fread1(_Format, _F, _Sup, _U, eof, _N, _Res, _AllFormat) -> %% This is an error as there is no more input. @@ -386,26 +386,16 @@ fread_string_cs(Line0, N0, true) -> %% fread_digits(Line, N, Base, Characters) %% Read segments of things, return "thing" characters in reverse order. -fread_skip_white([$\s|Line]) -> fread_skip_white(Line); -fread_skip_white([$\t|Line]) -> fread_skip_white(Line); -fread_skip_white([$\r|Line]) -> fread_skip_white(Line); -fread_skip_white([$\n|Line]) -> fread_skip_white(Line); +fread_skip_white([C|Line]) when ?is_whitespace(C) -> + fread_skip_white(Line); fread_skip_white(Line) -> Line. -fread_skip_white([$\s|Line], N) -> - fread_skip_white(Line, N+1); -fread_skip_white([$\t|Line], N) -> - fread_skip_white(Line, N+1); -fread_skip_white([$\r|Line], N) -> - fread_skip_white(Line, N+1); -fread_skip_white([$\n|Line], N) -> +fread_skip_white([C|Line], N) when ?is_whitespace(C) -> fread_skip_white(Line, N+1); fread_skip_white(Line, N) -> {Line,N}. -fread_skip_latin1_nonwhite([$\s|Line], N, Cs) -> {[$\s|Line],N,Cs}; -fread_skip_latin1_nonwhite([$\t|Line], N, Cs) -> {[$\t|Line],N,Cs}; -fread_skip_latin1_nonwhite([$\r|Line], N, Cs) -> {[$\r|Line],N,Cs}; -fread_skip_latin1_nonwhite([$\n|Line], N, Cs) -> {[$\n|Line],N,Cs}; +fread_skip_latin1_nonwhite([C|Line], N, Cs) when ?is_whitespace(C) -> + {[C|Line],N,Cs}; fread_skip_latin1_nonwhite([C|Line], N, []) when C > 255 -> {[C|Line],N,error}; fread_skip_latin1_nonwhite([C|Line], N, Cs) when C > 255 -> @@ -414,10 +404,8 @@ fread_skip_latin1_nonwhite([C|Line], N, Cs) -> fread_skip_latin1_nonwhite(Line, N+1, [C|Cs]); fread_skip_latin1_nonwhite([], N, Cs) -> {[],N,Cs}. -fread_skip_nonwhite([$\s|Line], N, Cs) -> {[$\s|Line],N,Cs}; -fread_skip_nonwhite([$\t|Line], N, Cs) -> {[$\t|Line],N,Cs}; -fread_skip_nonwhite([$\r|Line], N, Cs) -> {[$\r|Line],N,Cs}; -fread_skip_nonwhite([$\n|Line], N, Cs) -> {[$\n|Line],N,Cs}; +fread_skip_nonwhite([C|Line], N, Cs) when ?is_whitespace(C) -> + {[C|Line],N,Cs}; fread_skip_nonwhite([C|Line], N, Cs) -> fread_skip_nonwhite(Line, N+1, [C|Cs]); fread_skip_nonwhite([], N, Cs) -> {[],N,Cs}. diff --git a/lib/stdlib/src/queue.erl b/lib/stdlib/src/queue.erl index 4c6b4d710b..afe917b151 100644 --- a/lib/stdlib/src/queue.erl +++ b/lib/stdlib/src/queue.erl @@ -56,16 +56,14 @@ new() -> {[],[]}. %{RearList,FrontList} %% O(1) --spec is_queue(Term) -> boolean() when - Term :: term(). +-spec is_queue(Term :: term()) -> boolean(). is_queue({R,F}) when is_list(R), is_list(F) -> true; is_queue(_) -> false. %% O(1) --spec is_empty(Q) -> boolean() when - Q :: queue(). +-spec is_empty(Q :: queue()) -> boolean(). is_empty({[],[]}) -> true; is_empty({In,Out}) when is_list(In), is_list(Out) -> @@ -74,16 +72,14 @@ is_empty(Q) -> erlang:error(badarg, [Q]). %% O(len(Q)) --spec len(Q) -> non_neg_integer() when - Q :: queue(). +-spec len(Q :: queue()) -> non_neg_integer(). len({R,F}) when is_list(R), is_list(F) -> length(R)+length(F); len(Q) -> erlang:error(badarg, [Q]). %% O(len(Q)) --spec to_list(Q) -> list() when - Q :: queue(). +-spec to_list(Q :: queue()) -> list(). to_list({In,Out}) when is_list(In), is_list(Out) -> Out++lists:reverse(In, []); to_list(Q) -> @@ -92,8 +88,7 @@ to_list(Q) -> %% Create queue from list %% %% O(length(L)) --spec from_list(L) -> queue() when - L :: list(). +-spec from_list(L :: list()) -> queue(). from_list(L) when is_list(L) -> f2r(L); from_list(L) -> @@ -102,9 +97,7 @@ from_list(L) -> %% Return true or false depending on if element is in queue %% %% O(length(Q)) worst case --spec member(Item, Q) -> boolean() when - Item :: term(), - Q :: queue(). +-spec member(Item :: term(), Q :: queue()) -> boolean(). member(X, {R,F}) when is_list(R), is_list(F) -> lists:member(X, R) orelse lists:member(X, F); member(X, Q) -> @@ -117,10 +110,7 @@ member(X, Q) -> %% Put at least one element in each list, if it is cheap %% %% O(1) --spec in(Item, Q1) -> Q2 when - Item :: term(), - Q1 :: queue(), - Q2 :: queue(). +-spec in(Item :: term(), Q1 :: queue()) -> Q2 :: queue(). in(X, {[_]=In,[]}) -> {[X], In}; in(X, {In,Out}) when is_list(In), is_list(Out) -> @@ -132,10 +122,7 @@ in(X, Q) -> %% Put at least one element in each list, if it is cheap %% %% O(1) --spec in_r(Item, Q1) -> Q2 when - Item :: term(), - Q1 :: queue(), - Q2 :: queue(). +-spec in_r(Item :: term(), Q1 :: queue()) -> Q2 :: queue(). in_r(X, {[],[_]=F}) -> {F,[X]}; in_r(X, {R,F}) when is_list(R), is_list(F) -> @@ -146,10 +133,9 @@ in_r(X, Q) -> %% Take from head/front %% %% O(1) amortized, O(len(Q)) worst case --spec out(Q1) -> Result when - Q1 :: queue(), - Q2 :: queue(), - Result :: {{value, Item :: term()}, Q2} | {empty, Q1}. +-spec out(Q1 :: queue()) -> + {{value, Item :: term()}, Q2 :: queue()} | + {empty, Q1 :: queue()}. out({[],[]}=Q) -> {empty,Q}; out({[V],[]}) -> @@ -167,10 +153,9 @@ out(Q) -> %% Take from tail/rear %% %% O(1) amortized, O(len(Q)) worst case --spec out_r(Q1) -> Result when - Q1 :: queue(), - Q2 :: queue(), - Result :: {{value, Item :: term()}, Q2} | {empty, Q1}. +-spec out_r(Q1 :: queue()) -> + {{value, Item :: term()}, Q2 :: queue()} | + {empty, Q1 :: queue()}. out_r({[],[]}=Q) -> {empty,Q}; out_r({[],[V]}) -> @@ -191,9 +176,7 @@ out_r(Q) -> %% Return the first element in the queue %% %% O(1) since the queue is supposed to be well formed --spec get(Q) -> Item when - Q :: queue(), - Item :: term(). +-spec get(Q :: queue()) -> Item :: term(). get({[],[]}=Q) -> erlang:error(empty, [Q]); get({R,F}) when is_list(R), is_list(F) -> @@ -212,9 +195,7 @@ get([_|R], []) -> % malformed queue -> O(len(Q)) %% Return the last element in the queue %% %% O(1) since the queue is supposed to be well formed --spec get_r(Q) -> Item when - Q :: queue(), - Item :: term(). +-spec get_r(Q :: queue()) -> Item :: term(). get_r({[],[]}=Q) -> erlang:error(empty, [Q]); get_r({[H|_],F}) when is_list(F) -> @@ -229,9 +210,7 @@ get_r(Q) -> %% Return the first element in the queue %% %% O(1) since the queue is supposed to be well formed --spec peek(Q) -> 'empty' | {'value',Item} when - Q :: queue(), - Item :: term(). +-spec peek(Q :: queue()) -> empty | {value,Item :: term()}. peek({[],[]}) -> empty; peek({R,[H|_]}) when is_list(R) -> @@ -246,9 +225,7 @@ peek(Q) -> %% Return the last element in the queue %% %% O(1) since the queue is supposed to be well formed --spec peek_r(Q) -> 'empty' | {'value',Item} when - Q :: queue(), - Item :: term(). +-spec peek_r(Q :: queue()) -> empty | {value,Item :: term()}. peek_r({[],[]}) -> empty; peek_r({[H|_],F}) when is_list(F) -> @@ -263,9 +240,7 @@ peek_r(Q) -> %% Remove the first element and return resulting queue %% %% O(1) amortized --spec drop(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec drop(Q1 :: queue()) -> Q2 :: queue(). drop({[],[]}=Q) -> erlang:error(empty, [Q]); drop({[_],[]}) -> @@ -283,9 +258,7 @@ drop(Q) -> %% Remove the last element and return resulting queue %% %% O(1) amortized --spec drop_r(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec drop_r(Q1 :: queue()) -> Q2 :: queue(). drop_r({[],[]}=Q) -> erlang:error(empty, [Q]); drop_r({[],[_]}) -> @@ -306,9 +279,7 @@ drop_r(Q) -> %% Return reversed queue %% %% O(1) --spec reverse(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec reverse(Q1 :: queue()) -> Q2 :: queue(). reverse({R,F}) when is_list(R), is_list(F) -> {F,R}; reverse(Q) -> @@ -318,10 +289,7 @@ reverse(Q) -> %% %% Q2 empty: O(1) %% else: O(len(Q1)) --spec join(Q1, Q2) -> Q3 when - Q1 :: queue(), - Q2 :: queue(), - Q3 :: queue(). +-spec join(Q1 :: queue(), Q2 :: queue()) -> Q3 :: queue(). join({R,F}=Q, {[],[]}) when is_list(R), is_list(F) -> Q; join({[],[]}, {R,F}=Q) when is_list(R), is_list(F) -> @@ -335,11 +303,8 @@ join(Q1, Q2) -> %% %% N = 0..len(Q) %% O(max(N, len(Q))) --spec split(N, Q1) -> {Q2,Q3} when - N :: non_neg_integer(), - Q1 :: queue(), - Q2 :: queue(), - Q3 :: queue(). +-spec split(N :: non_neg_integer(), Q1 :: queue()) -> + {Q2 :: queue(),Q3 :: queue()}. split(0, {R,F}=Q) when is_list(R), is_list(F) -> {{[],[]},Q}; split(N, {R,F}=Q) when is_integer(N), N >= 1, is_list(R), is_list(F) -> @@ -380,10 +345,8 @@ split_r1_to_f2(N, [X|R1], F1, R2, F2) -> %% %% Fun(_) -> List: O(length(List) * len(Q)) %% else: O(len(Q) --spec filter(Fun, Q1) -> Q2 when - Fun :: fun((Item :: term()) -> boolean() | list()), - Q1 :: queue(), - Q2 :: queue(). +-spec filter(Fun, Q1 :: queue()) -> Q2 :: queue() when + Fun :: fun((Item :: term()) -> boolean() | list()). filter(Fun, {R0,F0}) when is_function(Fun, 1), is_list(R0), is_list(F0) -> F = filter_f(Fun, F0), R = filter_r(Fun, R0), @@ -459,10 +422,7 @@ filter_r(Fun, [X|R0]) -> %% Cons to head %% --spec cons(Item, Q1) -> Q2 when - Item :: term(), - Q1 :: queue(), - Q2 :: queue(). +-spec cons(Item :: term(), Q1 :: queue()) -> Q2 :: queue(). cons(X, Q) -> in_r(X, Q). @@ -471,9 +431,7 @@ cons(X, Q) -> %% Return the first element in the queue %% %% O(1) since the queue is supposed to be well formed --spec head(Q) -> Item when - Q :: queue(), - Item :: term(). +-spec head(Q :: queue()) -> Item :: term(). head({[],[]}=Q) -> erlang:error(empty, [Q]); head({R,F}) when is_list(R), is_list(F) -> @@ -483,9 +441,7 @@ head(Q) -> %% Remove head element and return resulting queue %% --spec tail(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec tail(Q1 :: queue()) -> Q2 :: queue(). tail(Q) -> drop(Q). @@ -493,35 +449,22 @@ tail(Q) -> %% Cons to tail %% --spec snoc(Q1, Item) -> Q2 when - Q1 :: queue(), - Q2 :: queue(), - Item :: term(). +-spec snoc(Q1 :: queue(), Item :: term()) -> Q2 :: queue(). snoc(Q, X) -> in(X, Q). %% Return last element --spec daeh(Q) -> Item when - Q :: queue(), - Item :: term(). +-spec daeh(Q :: queue()) -> Item :: term(). daeh(Q) -> get_r(Q). --spec last(Q) -> Item when - Q :: queue(), - Item :: term(). +-spec last(Q :: queue()) -> Item :: term(). last(Q) -> get_r(Q). %% Remove last element and return resulting queue --spec liat(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec liat(Q1 :: queue()) -> Q2 :: queue(). liat(Q) -> drop_r(Q). --spec lait(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec lait(Q1 :: queue()) -> Q2 :: queue(). lait(Q) -> drop_r(Q). %% Oops, mis-spelled 'tail' reversed. Forget this one. --spec init(Q1) -> Q2 when - Q1 :: queue(), - Q2 :: queue(). +-spec init(Q1 :: queue()) -> Q2 :: queue(). init(Q) -> drop_r(Q). %%-------------------------------------------------------------------------- diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl index 89fae05e4f..689e42051f 100644 --- a/lib/stdlib/src/timer.erl +++ b/lib/stdlib/src/timer.erl @@ -199,9 +199,9 @@ tc(M, F, A) -> %% Calculate the time difference (in microseconds) of two %% erlang:now() timestamps, T2-T1. %% --spec now_diff(T1, T2) -> Tdiff when - T1 :: calendar:t_now(), - T2 :: calendar:t_now(), +-spec now_diff(T2, T1) -> Tdiff when + T1 :: erlang:timestamp(), + T2 :: erlang:timestamp(), Tdiff :: integer(). now_diff({A2, B2, C2}, {A1, B1, C1}) -> ((A2-A1)*1000000 + B2-B1)*1000000 + C2-C1. diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl index 524d709431..c82c8159b6 100644 --- a/lib/stdlib/src/zip.erl +++ b/lib/stdlib/src/zip.erl @@ -223,7 +223,7 @@ openzip_open(F, Options) -> do_openzip_open(F, Options) -> Opts = get_openzip_options(Options), #openzip_opts{output = Output, open_opts = OpO, cwd = CWD} = Opts, - Input = get_zip_input(F), + Input = get_input(F), In0 = Input({open, F, OpO -- [write]}, []), {[#zip_comment{comment = C} | Files], In1} = get_central_dir(In0, fun raw_file_info_etc/5, Input), @@ -489,7 +489,7 @@ do_list_dir(F, Options) -> %% Print zip directory in short form -spec(t(Archive) -> ok when - Archive :: file:name() | binary | ZipHandle, + Archive :: file:name() | binary() | ZipHandle, ZipHandle :: pid()). t(F) when is_pid(F) -> zip_t(F); @@ -513,7 +513,7 @@ do_t(F, RawPrint) -> %% Print zip directory in long form (like ls -l) -spec(tt(Archive) -> ok when - Archive :: file:name() | binary | ZipHandle, + Archive :: file:name() | binary() | ZipHandle, ZipHandle :: pid()). tt(F) when is_pid(F) -> zip_tt(F); @@ -1174,7 +1174,7 @@ zip_get(Pid) when is_pid(Pid) -> zip_close(Pid) when is_pid(Pid) -> request(self(), Pid, close). --spec(zip_get(FileName, ZipHandle) -> {ok, [Result]} | {error, Reason} when +-spec(zip_get(FileName, ZipHandle) -> {ok, Result} | {error, Reason} when FileName :: file:name(), ZipHandle :: pid(), Result :: file:name() | {file:name(), binary()}, @@ -1183,7 +1183,7 @@ zip_close(Pid) when is_pid(Pid) -> zip_get(FileName, Pid) when is_pid(Pid) -> request(self(), Pid, {get, FileName}). --spec(zip_list_dir(ZipHandle) -> Result | {error, Reason} when +-spec(zip_list_dir(ZipHandle) -> {ok, Result} | {error, Reason} when Result :: [zip_comment() | zip_file()], ZipHandle :: pid(), Reason :: term()). diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl index 31a4f94294..4298b2c701 100644 --- a/lib/stdlib/test/erl_scan_SUITE.erl +++ b/lib/stdlib/test/erl_scan_SUITE.erl @@ -737,6 +737,10 @@ set_attribute() -> (catch {foo, erl_scan:set_attribute(line, [], F2)}), % type error ?line {'EXIT',{badarg,_}} = (catch {foo, erl_scan:set_attribute(column, [], F2)}), % type error + + %% OTP-9412 + ?line 8 = erl_scan:set_attribute(line, [{line,{nos,'X',8}}], + fun({nos,_V,VL}) -> VL end), ok. column_errors() -> diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 9d348b5f1a..9341300f90 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -72,6 +72,7 @@ exit_many_many_tables_owner/1]). -export([write_concurrency/1, heir/1, give_away/1, setopts/1]). -export([bad_table/1, types/1]). +-export([otp_9423/1]). -export([init_per_testcase/2, end_per_testcase/2]). %% Convenience for manual testing @@ -143,7 +144,8 @@ all() -> otp_8166, exit_large_table_owner, exit_many_large_table_owner, exit_many_tables_owner, exit_many_many_tables_owner, write_concurrency, heir, - give_away, setopts, bad_table, types]. + give_away, setopts, bad_table, types, + otp_9423]. groups() -> [{new, [], @@ -5420,7 +5422,39 @@ types_do(Opts) -> ?line verify_etsmem(EtsMem). - +otp_9423(doc) -> ["vm-deadlock caused by race between ets:delete and others on write_concurrency table"]; +otp_9423(Config) when is_list(Config) -> + InitF = fun(_) -> {0,0} end, + ExecF = fun({S,F}) -> + receive + stop -> + io:format("~p got stop\n", [self()]), + [end_of_work | {"Succeded=",S,"Failed=",F}] + after 0 -> + %%io:format("~p (~p) doing lookup\n", [self(), {S,F}]), + try ets:lookup(otp_9423, key) of + [] -> {S+1,F} + catch + error:badarg -> {S,F+1} + end + end + end, + FiniF = fun(R) -> R end, + case run_workers(InitF, ExecF, FiniF, infinite, 1) of + Pids when is_list(Pids) -> + %%[P ! start || P <- Pids], + repeat(fun() -> ets:new(otp_9423, [named_table, public, {write_concurrency,true}]), + ets:delete(otp_9423) + end, 10000), + [P ! stop || P <- Pids], + wait_pids(Pids), + ok; + + Skipped -> Skipped + end. + + + % % Utility functions: @@ -5434,21 +5468,30 @@ add_lists([E1|T1], [E2|T2], Acc) -> add_lists(T1, T2, [E1+E2 | Acc]). run_workers(InitF,ExecF,FiniF,Laps) -> + run_workers(InitF,ExecF,FiniF,Laps, 0). +run_workers(InitF,ExecF,FiniF,Laps, Exclude) -> case erlang:system_info(smp_support) of true -> - run_workers_do(InitF,ExecF,FiniF,Laps); + run_workers_do(InitF,ExecF,FiniF,Laps, Exclude); false -> {skipped,"No smp support"} end. - + run_workers_do(InitF,ExecF,FiniF,Laps) -> - NumOfProcs = erlang:system_info(schedulers), + run_workers_do(InitF,ExecF,FiniF,Laps, 0). +run_workers_do(InitF,ExecF,FiniF,Laps, Exclude) -> + ?line NumOfProcs = case erlang:system_info(schedulers) of + N when (N > Exclude) -> N - Exclude + end, io:format("smp starting ~p workers\n",[NumOfProcs]), Seeds = [{ProcN,random:uniform(9999)} || ProcN <- lists:seq(1,NumOfProcs)], Parent = self(), Pids = [spawn_link(fun()-> worker(Seed,InitF,ExecF,FiniF,Laps,Parent,NumOfProcs) end) || Seed <- Seeds], - wait_pids(Pids). + case Laps of + infinite -> Pids; + _ -> wait_pids(Pids) + end. worker({ProcN,Seed}, InitF, ExecF, FiniF, Laps, Parent, NumOfProcs) -> io:format("smp worker ~p, seed=~p~n",[self(),Seed]), @@ -5463,6 +5506,8 @@ worker_loop(0, _, State) -> State; worker_loop(_, _, [end_of_work|State]) -> State; +worker_loop(infinite, ExecF, State) -> + worker_loop(infinite,ExecF,ExecF(State)); worker_loop(N, ExecF, State) -> worker_loop(N-1,ExecF,ExecF(State)). @@ -5517,20 +5562,21 @@ etsmem() -> case erlang:system_info({allocator,ets_alloc}) of false -> undefined; MemInfo -> - MSBCS = lists:foldl( - fun ({instance, _, L}, Acc) -> - {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L), - {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L), - [MBCS,SBCS | Acc] - end, - [], - MemInfo), + CS = lists:foldl( + fun ({instance, _, L}, Acc) -> + {value,{_,SBMBCS}} = lists:keysearch(sbmbcs, 1, L), + {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L), + {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L), + [SBMBCS,MBCS,SBCS | Acc] + end, + [], + MemInfo), lists:foldl( fun(L, {Bl0,BlSz0}) -> {value,{_,Bl,_,_}} = lists:keysearch(blocks, 1, L), {value,{_,BlSz,_,_}} = lists:keysearch(blocks_size, 1, L), {Bl0+Bl,BlSz0+BlSz} - end, {0,0}, MSBCS) + end, {0,0}, CS) end}, {Mem,AllTabs}. diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl index 54a98985cd..bb02a879c2 100644 --- a/lib/stdlib/test/io_SUITE.erl +++ b/lib/stdlib/test/io_SUITE.erl @@ -27,7 +27,7 @@ otp_6282/1, otp_6354/1, otp_6495/1, otp_6517/1, otp_6502/1, manpage/1, otp_6708/1, otp_7084/1, otp_7421/1, io_lib_collect_line_3_wb/1, cr_whitespace_in_string/1, - io_fread_newlines/1, otp_8989/1]). + io_fread_newlines/1, otp_8989/1, io_lib_fread_literal/1]). %-define(debug, true). @@ -62,7 +62,7 @@ all() -> otp_6282, otp_6354, otp_6495, otp_6517, otp_6502, manpage, otp_6708, otp_7084, otp_7421, io_lib_collect_line_3_wb, cr_whitespace_in_string, - io_fread_newlines, otp_8989]. + io_fread_newlines, otp_8989, io_lib_fread_literal]. groups() -> []. @@ -1995,3 +1995,29 @@ otp_8989(Suite) when is_list(Suite) -> ?line "Hel " = fmt("~-4.*s", [3,Hello]), ?line "Hel " = fmt("~*.*s", [-4,3,Hello]), ok. + +io_lib_fread_literal(doc) -> + "OTP-9439 io_lib:fread bug for literate at end"; +io_lib_fread_literal(Suite) when is_list(Suite) -> + ?line {more,"~d",0,""} = io_lib:fread("~d", ""), + ?line {error,{fread,integer}} = io_lib:fread("~d", " "), + ?line {more,"~d",1,""} = io_lib:fread(" ~d", " "), + ?line {ok,[17],"X"} = io_lib:fread(" ~d", " 17X"), + %% + ?line {more,"d",0,""} = io_lib:fread("d", ""), + ?line {error,{fread,input}} = io_lib:fread("d", " "), + ?line {more,"d",1,""} = io_lib:fread(" d", " "), + ?line {ok,[],"X"} = io_lib:fread(" d", " dX"), + %% + ?line {done,eof,_} = io_lib:fread([], eof, "~d"), + ?line {done,eof,_} = io_lib:fread([], eof, " ~d"), + ?line {more,C1} = io_lib:fread([], " \n", " ~d"), + ?line {done,{error,{fread,input}},_} = io_lib:fread(C1, eof, " ~d"), + ?line {done,{ok,[18]},""} = io_lib:fread(C1, "18\n", " ~d"), + %% + ?line {done,eof,_} = io_lib:fread([], eof, "d"), + ?line {done,eof,_} = io_lib:fread([], eof, " d"), + ?line {more,C2} = io_lib:fread([], " \n", " d"), + ?line {done,{error,{fread,input}},_} = io_lib:fread(C2, eof, " d"), + ?line {done,{ok,[]},[]} = io_lib:fread(C2, "d\n", " d"), + ok. diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl index c79a5002fb..ff5e4c629a 100644 --- a/lib/stdlib/test/supervisor_SUITE.erl +++ b/lib/stdlib/test/supervisor_SUITE.erl @@ -114,10 +114,9 @@ end_per_group(_GroupName, Config) -> Config. init_per_testcase(count_children_memory, Config) -> - MemoryState = erlang:system_info(allocator), - case count_children_allocator_test(MemoryState) of - true -> Config; - false -> + try erlang:memory() of + _ -> Config + catch error:notsup -> {skip, "+Meamin used during test; erlang:memory/1 not available"} end; init_per_testcase(_Case, Config) -> @@ -1032,17 +1031,6 @@ count_children_memory(Config) when is_list(Config) -> [terminate(SupPid, Pid, child, kill) || {undefined, Pid, worker, _Modules} <- Children3], [1,0,0,0] = get_child_counts(sup_test). -count_children_allocator_test(MemoryState) -> - Allocators = [temp_alloc, eheap_alloc, binary_alloc, ets_alloc, - driver_alloc, sl_alloc, ll_alloc, fix_alloc, std_alloc, - sys_alloc], - MemoryStateList = element(4, MemoryState), - AllocTypes = [lists:keyfind(Alloc, 1, MemoryStateList) - || Alloc <- Allocators], - AllocStates = [lists:keyfind(e, 1, AllocValue) - || {_Type, AllocValue} <- AllocTypes], - lists:all(fun(State) -> State == {e, true} end, AllocStates). - %%------------------------------------------------------------------------- do_not_save_start_parameters_for_temporary_children(doc) -> ["Temporary children shall not be restarted so they should not " diff --git a/lib/stdlib/test/zip_SUITE.erl b/lib/stdlib/test/zip_SUITE.erl index d5f2cd52d4..7233c061ef 100644 --- a/lib/stdlib/test/zip_SUITE.erl +++ b/lib/stdlib/test/zip_SUITE.erl @@ -375,7 +375,8 @@ zip_options(Config) when is_list(Config) -> ok = file:set_cwd(?config(data_dir, Config)), %% Create a zip archive - {ok, Zip} = zip:zip("filename_not_used.zip", Names, [memory, {cwd, PrivDir}]), + {ok, {_,Zip}} = + zip:zip("filename_not_used.zip", Names, [memory, {cwd, PrivDir}]), %% Open archive {ok, ZipSrv} = zip:zip_open(Zip, [memory]), diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml index 75ffa25311..17de66bb22 100644 --- a/lib/tools/doc/src/xref.xml +++ b/lib/tools/doc/src/xref.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2000</year><year>2010</year> + <year>2000</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -1465,8 +1465,8 @@ Evaluates a predefined analysis. <name>start(NameOrOptions) -> Return</name> <fsummary>Create an Xref server.</fsummary> <type> - <v>Name = atom()()</v> - <v>XrefOrOptions = Xref | Options</v> + <v>NameOrOptions = Name | Options</v> + <v>Name = atom()</v> <v>Options = [Option] | Option</v> <v>Option = {xref_mode, mode()} | term()</v> <v>Return = {ok, pid()} | {error, {already_started, pid()}}</v> @@ -1483,7 +1483,7 @@ Evaluates a predefined analysis. <name>start(Name, Options) -> Return</name> <fsummary>Create an Xref server.</fsummary> <type> - <v>Name = atom()()</v> + <v>Name = atom()</v> <v>Options = [Option] | Option</v> <v>Option = {xref_mode, mode()} | term()</v> <v>Return = {ok, pid()} | {error, {already_started, pid()}}</v> diff --git a/lib/xmerl/include/xmerl_xsd.hrl b/lib/xmerl/include/xmerl_xsd.hrl index b527accc8c..6dad7d8ff0 100644 --- a/lib/xmerl/include/xmerl_xsd.hrl +++ b/lib/xmerl/include/xmerl_xsd.hrl @@ -36,6 +36,7 @@ schema_name, vsn, schema_preprocessed=false, + external_xsd_base=false, xsd_base, xml_options=[], scope=[], diff --git a/lib/xmerl/src/xmerl_scan.erl b/lib/xmerl/src/xmerl_scan.erl index 059c8f21b6..e598c5f56d 100644 --- a/lib/xmerl/src/xmerl_scan.erl +++ b/lib/xmerl/src/xmerl_scan.erl @@ -2276,7 +2276,7 @@ scan_att_chars([H|T], S0, H, Acc, TmpAcc,AttType,IsNorm) -> % End quote true -> normalize(Acc,S,IsNorm) end, - {lists:reverse(Acc2), T, S2,IsNorm2}; + {lists:flatten(lists:reverse(Acc2)), T, S2,IsNorm2}; scan_att_chars("&" ++ T, S0, Delim, Acc, TmpAcc,AT,IsNorm) -> % Reference ?bump_col(1), {ExpRef, T1, S1} = scan_reference(T, S), diff --git a/lib/xmerl/src/xmerl_xsd.erl b/lib/xmerl/src/xmerl_xsd.erl index e56f1470c0..f003cc74ba 100644 --- a/lib/xmerl/src/xmerl_xsd.erl +++ b/lib/xmerl/src/xmerl_xsd.erl @@ -287,10 +287,19 @@ process_schema(Schema) -> %% error reason. The error reason may be a list of several errors %% or a single error encountered during the processing. process_schema(Schema,Options) when is_list(Options) -> - S = initiate_state(Options,Schema), - process_schema2(xmerl_scan:file(filename:join(S#xsd_state.xsd_base, Schema)),S,Schema); -process_schema(Schema,State) when is_record(State,xsd_state) -> - process_schema2(xmerl_scan:file(filename:join(State#xsd_state.xsd_base, Schema)),State,Schema). + State = initiate_state(Options,Schema), + process_schema(Schema, State); +process_schema(Schema, State=#xsd_state{fetch_fun=Fetch})-> + case Fetch(Schema, State) of + {ok,{file,File},_} -> + process_schema2(xmerl_scan:file(File), State, Schema); + {ok,{string,Str},_} -> + process_schema2(xmerl_scan:string(Str), State, Schema); + {ok,[],_} -> + {error,enoent}; + Err -> + Err + end. process_schema2(Err={error,_},_,_) -> Err; @@ -319,12 +328,9 @@ process_schemas(Schemas) -> %% error reason. The error reason may be a list of several errors %% or a single error encountered during the processing. process_schemas(Schemas=[{_,Schema}|_],Options) when is_list(Options) -> - process_schemas(Schemas,initiate_state(Options,Schema)); + State = initiate_state(Options,Schema), + process_schemas(Schemas, State); process_schemas([{_NS,Schema}|Rest],State=#xsd_state{fetch_fun=Fetch}) -> -%% case process_external_schema_once(Schema,if_list_to_atom(NS),State) of -%% S when is_record(S,xsd_state) -> -%% case process_schema(filename:join([State#xsd_state.xsd_base,Schema]),State) of -%% {ok,S} -> Res= case Fetch(Schema,State) of {ok,{file,File},_} -> @@ -345,20 +351,20 @@ process_schemas([{_NS,Schema}|Rest],State=#xsd_state{fetch_fun=Fetch}) -> process_schemas([],S) when is_record(S,xsd_state) -> {ok,S}. - initiate_state(Opts,Schema) -> XSDBase = filename:dirname(Schema), {{state,S},RestOpts}=new_state(Opts), S2 = create_tables(S), - initiate_state2(S2#xsd_state{schema_name = Schema, - xsd_base = XSDBase, - fetch_fun = fun fetch/2},RestOpts). + S3 = initiate_state2(S2#xsd_state{schema_name = Schema, xsd_base=XSDBase, + fetch_fun = fun fetch/2}, + RestOpts). + initiate_state2(S,[]) -> S; initiate_state2(S,[{tab2file,Bool}|T]) -> initiate_state2(S#xsd_state{tab2file=Bool},T); -initiate_state2(S,[{xsdbase,XSDBase}|T]) -> - initiate_state2(S#xsd_state{xsd_base=XSDBase},T); +initiate_state2(S,[{xsdbase, XSDBase}|T]) -> + initiate_state2(S#xsd_state{xsd_base=XSDBase, external_xsd_base=true},T); initiate_state2(S,[{fetch_fun,FetchFun}|T]) -> initiate_state2(S#xsd_state{fetch_fun=FetchFun},T); initiate_state2(S,[{fetch_path,FetchPath}|T]) -> @@ -5232,7 +5238,12 @@ fetch(URI,S) -> [] -> %% empty systemliteral []; _ -> - filename:join(S#xsd_state.xsd_base, URI) + case S#xsd_state.external_xsd_base of + true -> + filename:join(S#xsd_state.xsd_base, URI); + false -> + filename:join(S#xsd_state.xsd_base, filename:basename(URI)) + end end, Path = path_locate(S#xsd_state.fetch_path, Filename, Fullname), ?dbg("fetch(~p) -> {file, ~p}.~n", [URI, Path]), diff --git a/lib/xmerl/test/xmerl_SUITE.erl b/lib/xmerl/test/xmerl_SUITE.erl index 392b2522e8..0c809dbcb6 100644 --- a/lib/xmerl/test/xmerl_SUITE.erl +++ b/lib/xmerl/test/xmerl_SUITE.erl @@ -57,7 +57,8 @@ groups() -> {eventp_tests, [], [sax_parse_and_export]}, {ticket_tests, [], [ticket_5998, ticket_7211, ticket_7214, ticket_7430, - ticket_6873, ticket_7496, ticket_8156, ticket_8697]}, + ticket_6873, ticket_7496, ticket_8156, ticket_8697, + ticket_9411]}, {app_test, [], [{xmerl_app_test, all}]}, {appup_test, [], [{xmerl_appup_test, all}]}]. @@ -575,7 +576,17 @@ ticket_8697(Config) -> ?line [16#545C] = HexEntityText, ok. +ticket_9411(suite) -> []; +ticket_9411(doc) -> + ["Test that xmerl_scan handles attribute that contains for example ""]; +ticket_9411(Config) -> + DataDir = ?config(data_dir,Config), + ?line {ok, Schema} = xmerl_xsd:process_schema(filename:join([DataDir,"misc/ticket_9411.xsd"])), + ?line {ok, Bin} = file:read_file(filename:join([DataDir,"misc/ticket_9411.xml"])), + ?line Xml = erlang:binary_to_list(Bin), + ?line {E, _} = xmerl_scan:string(Xml), + ?line {E, _} = xmerl_xsd:validate(E, Schema). diff --git a/lib/xmerl/test/xmerl_SUITE_data/misc.tar.gz b/lib/xmerl/test/xmerl_SUITE_data/misc.tar.gz Binary files differindex c48a6f897b..fef7431845 100644 --- a/lib/xmerl/test/xmerl_SUITE_data/misc.tar.gz +++ b/lib/xmerl/test/xmerl_SUITE_data/misc.tar.gz diff --git a/lib/xmerl/test/xmerl_xsd_SUITE.erl b/lib/xmerl/test/xmerl_xsd_SUITE.erl index a0d3b1e667..421fa48054 100644 --- a/lib/xmerl/test/xmerl_xsd_SUITE.erl +++ b/lib/xmerl/test/xmerl_xsd_SUITE.erl @@ -62,7 +62,7 @@ groups() -> sis2, state2file_file2state, union]}, {ticket_tests, [], [ticket_6910, ticket_7165, ticket_7190, ticket_7288, - ticket_7736, ticket_8599]}, + ticket_7736, ticket_8599, ticket_9410]}, {facets, [], [length, minLength, maxLength, pattern, enumeration, whiteSpace, maxInclusive, maxExclusive, minExclusive, @@ -1146,3 +1146,8 @@ ticket_8599(Config) -> ?line {{xmlElement,persons,persons,_,_,_,_,_,_,_,_,_},_GlobalState} = xmerl_xsd:validate(E, S). + +ticket_9410(suite) -> []; +ticket_9410(Config) -> + file:set_cwd(filename:join([?config(data_dir,Config),".."])), + ?line {ok, _S} = xmerl_xsd:process_schema("xmerl_xsd_SUITE_data/small.xsd"). |