diff options
Diffstat (limited to 'lib')
323 files changed, 7520 insertions, 4921 deletions
diff --git a/lib/asn1/doc/src/asn1_getting_started.xml b/lib/asn1/doc/src/asn1_getting_started.xml index 3c8ec24723..d40b294c39 100644 --- a/lib/asn1/doc/src/asn1_getting_started.xml +++ b/lib/asn1/doc/src/asn1_getting_started.xml @@ -678,7 +678,7 @@ ok 1081,32,1043,1085,1086,1084]</pre> <p>For details, see the <seealso marker="stdlib:unicode">unicode</seealso> - module in <c>stdlib</c>.</p> + module in STDLIB.</p> <p>In the following example, this ASN.1 specification is used:</p> <pre> diff --git a/lib/asn1/doc/src/asn1_introduction.xml b/lib/asn1/doc/src/asn1_introduction.xml index d8b81aa467..e4f406364d 100644 --- a/lib/asn1/doc/src/asn1_introduction.xml +++ b/lib/asn1/doc/src/asn1_introduction.xml @@ -30,7 +30,7 @@ <file>asn1_introduction.xml</file> </header> - <p>The <c>ASN.1</c> application provides the following:</p> + <p>The ASN.1 application provides the following:</p> <list type="bulleted"> <item>An ASN.1 compiler for Erlang, which generates encode and diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index 68d335f451..ac3d9c828e 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -329,7 +329,7 @@ </item> <item> <p> - The <c>asn1</c> application would fail to build if the + The ASN.1 application would fail to build if the <c>.erlang</c> file printed something to standard output.</p> <p> Own Id: OTP-11360</p> @@ -997,7 +997,7 @@ also been extended. </item><item> The <c>configure</c> scripts of <c>erl_interface</c> and <c>odbc</c> now search for thread libraries and thread library quirks the - same way as <c>erts</c> do. </item><item> The + same way as ERTS do. </item><item> The <c>configure</c> script of the <c>odbc</c> application now also looks for odbc libraries in <c>lib64</c> and <c>lib/64</c> directories when building on a 64-bit diff --git a/lib/asn1/src/asn1_db.erl b/lib/asn1/src/asn1_db.erl index 557eca0ffd..869ea310aa 100644 --- a/lib/asn1/src/asn1_db.erl +++ b/lib/asn1/src/asn1_db.erl @@ -106,7 +106,9 @@ loop(#state{parent = Parent, monitor = MRef, table = Table, loop(State); {save, OutFile, Mod} -> [{_,Mtab}] = ets:lookup(Table, Mod), - ok = ets:tab2file(Mtab, OutFile), + TempFile = OutFile ++ ".#temp", + ok = ets:tab2file(Mtab, TempFile), + ok = file:rename(TempFile, OutFile), loop(State); {From, {new, Mod, Erule}} -> [] = ets:lookup(Table, Mod), %Assertion. diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl index dd269f095d..8783b5418d 100644 --- a/lib/asn1/src/asn1ct.erl +++ b/lib/asn1/src/asn1ct.erl @@ -221,9 +221,8 @@ check_pass(#st{code=M,file=File,includes=Includes, {error,St#st{error=Reason}} end. -save_pass(#st{code=M,erule=Erule,dbfile=DbFile}=St) -> +save_pass(#st{code=M,erule=Erule}=St) -> ok = asn1ct_check:storeindb(#state{erule=Erule}, M), - asn1_db:dbsave(DbFile,M#module.name), {ok,St}. parse_listing(#st{code=Code,outfile=OutFile0}=St) -> diff --git a/lib/asn1/test/asn1_test_lib.erl b/lib/asn1/test/asn1_test_lib.erl index 1b4c3b3c77..dc614db4f2 100644 --- a/lib/asn1/test/asn1_test_lib.erl +++ b/lib/asn1/test/asn1_test_lib.erl @@ -34,12 +34,16 @@ run_dialyzer() -> compile(File, Config, Options) -> compile_all([File], Config, Options). -compile_all(Files, Config, Options) -> +compile_all(Files, Config, Options0) -> DataDir = proplists:get_value(data_dir, Config), CaseDir = proplists:get_value(case_dir, Config), - [compile_file(filename:join(DataDir, F), [{outdir, CaseDir}, - debug_info|Options]) - || F <- Files], + Options = [{outdir,CaseDir},debug_info|Options0], + + Comp = fun(F) -> + compile_file(filename:join(DataDir, F), Options) + end, + p_run(Comp, Files), + dialyze(Files, Options), ok. @@ -94,9 +98,9 @@ compile_file(File, Options) -> try ok = asn1ct:compile(File, [warnings_as_errors|Options]) catch - Class:Reason -> - ct:print("Failed to compile ~s\n", [File]), - erlang:error({compile_failed, {File, Options}, {Class, Reason}}) + _:Reason -> + ct:print("Failed to compile ~s\n~p", [File,Reason]), + error end. compile_erlang(Mod, Config, Options) -> @@ -219,3 +223,38 @@ ber_get_len(<<0:1,L:7,T/binary>>) -> ber_get_len(<<1:1,Octets:7,T0/binary>>) -> <<L:Octets/unit:8,T/binary>> = T0, {L,T}. + +%% p_run(fun(Data) -> ok|error, List) -> ok +%% Will fail the test case if there were any errors. + +p_run(Test, List) -> + S = erlang:system_info(schedulers), + N = case test_server:is_cover() of + false -> + S + 1; + true -> + %% Cover is running. Using too many processes + %% could slow us down. + min(S, 4) + end, + %%io:format("p_run: ~p parallel processes\n", [N]), + p_run_loop(Test, List, N, [], 0). + +p_run_loop(_, [], _, [], Errors) -> + case Errors of + 0 -> ok; + N -> ct:fail({N,errors}) + end; +p_run_loop(Test, [H|T], N, Refs, Errors) when length(Refs) < N -> + {_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end), + p_run_loop(Test, T, N, [Ref|Refs], Errors); +p_run_loop(Test, List, N, Refs0, Errors0) -> + receive + {'DOWN',Ref,process,_,Res} -> + Errors = case Res of + ok -> Errors0; + error -> Errors0+1 + end, + Refs = Refs0 -- [Ref], + p_run_loop(Test, List, N, Refs, Errors) + end. diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml index 3f83747485..48ffe653e4 100644 --- a/lib/common_test/doc/src/common_test_app.xml +++ b/lib/common_test/doc/src/common_test_app.xml @@ -566,7 +566,7 @@ (which also causes the test case process to terminate).</p> <p>Elements from the <c>Config</c> list can, for example, be read - with <c>proplists:get_value/2</c> in <c>STDLIB</c> + with <c>proplists:get_value/2</c> in STDLIB (or the macro <c>?config</c> defined in <c>ct.hrl</c>).</p> <p>If you decide not to run the test case after all, return diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml index 264bcff251..ffc64cba67 100644 --- a/lib/common_test/doc/src/ct.xml +++ b/lib/common_test/doc/src/ct.xml @@ -777,7 +777,7 @@ caught by any installed event manager.</p> <p>See also - <seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>.</p> + <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p> </desc> </func> @@ -1236,7 +1236,7 @@ <v>Millisecs = integer() | float()</v> </type> <desc><marker id="sleep-1"/> - <p>This function, similar to <c>timer:sleep/1</c> in <c>STDLIB</c>, + <p>This function, similar to <c>timer:sleep/1</c> in STDLIB, suspends the test case for a specified time. However, this function also multiplies <c>Time</c> with the <c>multiply_timetraps</c> value (if set) and under certain @@ -1330,7 +1330,7 @@ caught by any installed event manager.</p> <p>See also - <seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>. + <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>. </p> </desc> </func> diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml index 3b1e564b66..c2cf29c530 100644 --- a/lib/common_test/doc/src/ct_hooks.xml +++ b/lib/common_test/doc/src/ct_hooks.xml @@ -94,7 +94,7 @@ <seealso marker="#Module:id-1"><c>ct_hooks:id/1</c></seealso>, or a <c>reference</c> (created using <seealso marker="erts:erlang#make_ref-0">erlang:make_ref/0</seealso> - in <c>ERTS</c>) if + in ERTS) if <seealso marker="#Module:id-1"><c>ct_hooks:id/1</c></seealso> is not implemented.</p> diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml index 1998f15697..0e4c35e11f 100644 --- a/lib/common_test/doc/src/ct_hooks_chapter.xml +++ b/lib/common_test/doc/src/ct_hooks_chapter.xml @@ -374,7 +374,7 @@ <title>Example CTH</title> <p>The following CTH logs information about a test run into a format parseable by <seealso marker="kernel:file#consult-1">file:consult/1</seealso> - (in <c>Kernel</c>): + (in Kernel): </p> <code> %%% @doc Common Test Example Common Test Hook module. @@ -499,13 +499,13 @@ <tag><c>cth_log_redirect</c></tag> <item> <p>Built-in</p> - <p>Captures all <c>error_logger</c> and <c>SASL</c> logging + <p>Captures all <c>error_logger</c> and SASL logging events and prints them to the current test case log. If an event cannot be associated with a test case, it is printed in the <c>Common Test</c> framework log. This happens for test cases running in parallel and events occuring in-between test cases. You can configure the level of - <seealso marker="sasl:sasl_app"><c>SASL</c></seealso> events report - using the normal <c>SASL</c> mechanisms.</p> + <seealso marker="sasl:sasl_app">SASL</seealso> events report + using the normal SASL mechanisms.</p> </item> <tag><c>cth_surefire</c></tag> <item> diff --git a/lib/common_test/doc/src/ct_ssh.xml b/lib/common_test/doc/src/ct_ssh.xml index d00737aa5a..137e4c3f1d 100644 --- a/lib/common_test/doc/src/ct_ssh.xml +++ b/lib/common_test/doc/src/ct_ssh.xml @@ -64,7 +64,7 @@ <p><c>ConnType = ssh | sftp</c>.</p> <p>For other types, see - <seealso marker="ssh:ssh"><c>ssh:ssh(3)</c></seealso>.</p> + <seealso marker="ssh:ssh"><c>ssh(3)</c></seealso>.</p> <p>All time-out parameters in <c>ct_ssh</c> functions are values in milliseconds.</p> @@ -88,7 +88,7 @@ <tag><c>ssh_sftp_return() = term()</c></tag> <item><marker id="type-ssh_sftp_return"/> <p>Return value from an - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp</c></seealso> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp</c></seealso> function.</p></item> </taglist> </section> @@ -104,7 +104,7 @@ </type> <desc><marker id="apread-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -118,7 +118,7 @@ </type> <desc><marker id="apread-5"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -132,7 +132,7 @@ </type> <desc><marker id="apwrite-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -146,7 +146,7 @@ </type> <desc><marker id="apwrite-5"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -160,7 +160,7 @@ </type> <desc><marker id="aread-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -174,7 +174,7 @@ </type> <desc><marker id="aread-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -188,7 +188,7 @@ </type> <desc><marker id="awrite-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -202,7 +202,7 @@ </type> <desc><marker id="awrite-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -216,7 +216,7 @@ </type> <desc><marker id="close-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -230,7 +230,7 @@ </type> <desc><marker id="close-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -310,7 +310,7 @@ </type> <desc><marker id="del_dir-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -324,7 +324,7 @@ </type> <desc><marker id="del_dir-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -338,7 +338,7 @@ </type> <desc><marker id="delete-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -352,7 +352,7 @@ </type> <desc><marker id="delete-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -423,7 +423,7 @@ </type> <desc><marker id="get_file_info-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -437,7 +437,7 @@ </type> <desc><marker id="get_file_info-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -451,7 +451,7 @@ </type> <desc><marker id="list_dir-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -465,7 +465,7 @@ </type> <desc><marker id="list_dir-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -479,7 +479,7 @@ </type> <desc><marker id="make_dir-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -493,7 +493,7 @@ </type> <desc><marker id="make_dir-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -507,7 +507,7 @@ </type> <desc><marker id="make_symlink-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -521,7 +521,7 @@ </type> <desc><marker id="make_symlink-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -535,7 +535,7 @@ </type> <desc><marker id="open-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -549,7 +549,7 @@ </type> <desc><marker id="open-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -563,7 +563,7 @@ </type> <desc><marker id="opendir-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -577,7 +577,7 @@ </type> <desc><marker id="opendir-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -591,7 +591,7 @@ </type> <desc><marker id="position-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -605,7 +605,7 @@ </type> <desc><marker id="position-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -619,7 +619,7 @@ </type> <desc><marker id="pread-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -633,7 +633,7 @@ </type> <desc><marker id="pread-5"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -647,7 +647,7 @@ </type> <desc><marker id="pwrite-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -661,7 +661,7 @@ </type> <desc><marker id="pwrite-5"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -675,7 +675,7 @@ </type> <desc><marker id="read-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -689,7 +689,7 @@ </type> <desc><marker id="read-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -703,7 +703,7 @@ </type> <desc><marker id="read_file-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -717,7 +717,7 @@ </type> <desc><marker id="read_file-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -731,7 +731,7 @@ </type> <desc><marker id="read_file_info-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -745,7 +745,7 @@ </type> <desc><marker id="read_file_info-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -759,7 +759,7 @@ </type> <desc><marker id="read_link-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -773,7 +773,7 @@ </type> <desc><marker id="read_link-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -787,7 +787,7 @@ </type> <desc><marker id="read_link_info-2"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -801,7 +801,7 @@ </type> <desc><marker id="read_link_info-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -853,7 +853,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> <p>If <c>End</c> is a fun, this fun is called with one argument, the data value in a received <c>ssh_cm</c> message (see - <seealso marker="ssh:ssh_connection"><c>ssh:ssh_connection(3)</c></seealso>. + <seealso marker="ssh:ssh_connection"><c>ssh_connection(3)</c></seealso>. The fun is to return either <c>true</c> to end the receiving operation (and have the so far collected data returned) or <c>false</c> to wait for more data from the server. Even if a fun @@ -872,7 +872,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </type> <desc><marker id="rename-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -886,7 +886,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </type> <desc><marker id="rename-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1070,7 +1070,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1084,7 +1084,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1098,7 +1098,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write_file-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1112,7 +1112,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write_file-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1126,7 +1126,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write_file_info-3"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> @@ -1140,7 +1140,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </type> <desc><marker id="write_file_info-4"/> <p>For information and other types, see - <seealso marker="ssh:ssh_sftp"><c>ssh:ssh_sftp(3)</c></seealso>.</p> + <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p> </desc> </func> </funcs> diff --git a/lib/common_test/doc/src/ct_telnet.xml b/lib/common_test/doc/src/ct_telnet.xml index e2a45e894b..eba3c3030d 100644 --- a/lib/common_test/doc/src/ct_telnet.xml +++ b/lib/common_test/doc/src/ct_telnet.xml @@ -198,7 +198,7 @@ <item><marker id="type-prompt_regexp"/> <p>Regular expression matching all possible prompts for a specific target type. <c>regexp</c> must not have any groups, that is, when - matching, <c>re:run/3</c> (in <c>STDLIB</c>) must return a list with + matching, <c>re:run/3</c> (in STDLIB) must return a list with one single element.</p></item> </taglist> </section> diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml index 2978226a19..bd9ed21cb4 100644 --- a/lib/common_test/doc/src/event_handler_chapter.xml +++ b/lib/common_test/doc/src/event_handler_chapter.xml @@ -50,7 +50,7 @@ pass the information on. The event handlers are Erlang modules implemented by the <c>Common Test</c> user according to the <c>gen_event</c> behavior (for details, see module - <seealso marker="stdlib:gen_event"><c>stdlib:gen_event</c></seealso> and + <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso> and section <seealso marker="doc/design_principles:events"><c>gen_event Behaviour</c></seealso> in OTP Design Principles in the System Documentation). @@ -69,8 +69,8 @@ manager, either by telling <c>Common Test</c> to install them before the test run (described later), or by adding the handlers dynamically during the test run using - <seealso marker="stdlib:gen_event#add_handler-3"><c>stdlib:gen_event:add_handler/3</c></seealso> or - <seealso marker="stdlib:gen_event#add_sup_handler-3"><c>stdlib:gen_event:add_sup_handler/3</c></seealso>. + <seealso marker="stdlib:gen_event#add_handler-3"><c>gen_event:add_handler/3</c></seealso> or + <seealso marker="stdlib:gen_event#add_sup_handler-3"><c>gen_event:add_sup_handler/3</c></seealso>. In the latter scenario, the reference of the <c>Common Test</c> event manager is required. To get it, call <seealso marker="ct#get_event_mgr_ref-0"><c>ct:get_event_mgr_ref/0</c></seealso> diff --git a/lib/common_test/doc/src/introduction.xml b/lib/common_test/doc/src/introduction.xml index 40724f24e9..df12bea6dd 100644 --- a/lib/common_test/doc/src/introduction.xml +++ b/lib/common_test/doc/src/introduction.xml @@ -45,7 +45,7 @@ </list> <p><c>Common Test</c> also integrates use of the OTP <seealso marker="tools:cover">cover</seealso> tool in application - <c>Tools</c> for code coverage analysis of Erlang/OTP programs.</p> + Tools for code coverage analysis of Erlang/OTP programs.</p> <p><c>Common Test</c> executes test suite programs automatically, without operator interaction. Test progress and results are diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 43e36adfb6..76e306c4ed 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -274,7 +274,7 @@ <note><p>Directories passed to <c>Common Test</c> can have either relative or absolute paths.</p></note> - <note><p>Any start flags to the Erlang runtime system (application <c>ERTS</c>) can also be passed as + <note><p>Any start flags to the Erlang runtime system (application ERTS) can also be passed as parameters to <c>ct_run</c>. It is, for example, useful to be able to pass directories to be added to the Erlang code server search path with flag <c>-pa</c> or <c>-pz</c>. If you have common help- or library @@ -286,7 +286,7 @@ <p>The absolute path of directory <c>chat_server/ebin</c> is here passed to the code server. This is essential because relative paths are stored by the code server as relative, and <c>Common Test</c> changes - the current working directory of <c>ERTS</c> during the test run.</p> + the current working directory of ERTS during the test run.</p> </note> <p>The <c>ct_run</c> program sets the exit status before shutting down. The following values @@ -1258,7 +1258,7 @@ <p>The minor log files contain full details of every single test case, each in a separate file. This way, it is straightforward to compare the latest results to that of previous - test runs, even if the set of test cases changes. If application <c>SASL</c> + test runs, even if the set of test cases changes. If application SASL is running, its logs are also printed to the current minor log file by the <seealso marker="common_test:ct_hooks_chapter#builtin_cths"> cth_log_redirect built-in hook</seealso>. diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml index 83daf771a6..7bd2ccf588 100644 --- a/lib/common_test/doc/src/write_test_chapter.xml +++ b/lib/common_test/doc/src/write_test_chapter.xml @@ -269,10 +269,10 @@ <p>As parameter <c>Config</c> is a list of key-value tuples, that is, a data type called a property list, it can be handled by the - <seealso marker="stdlib:proplists"><c>stdlib:proplists</c></seealso> module. + <seealso marker="stdlib:proplists"><c>proplists</c></seealso> module. A value can, for example, be searched for and returned with function <seealso marker="stdlib:proplists#get_value-2"><c>proplists:get_value/2</c></seealso>. - Also, or alternatively, the general <seealso marker="stdlib:lists"><c>stdlib:lists</c></seealso> + Also, or alternatively, the general <seealso marker="stdlib:lists"><c>lists</c></seealso> module contains useful functions. Normally, the only operations performed on <c>Config</c> is insert (adding a tuple to the head of the list) and lookup. <c>Common Test</c> provides a simple macro named <c>?config</c>, @@ -652,7 +652,7 @@ <title>Parallel Test Cases and I/O</title> <p>A parallel test case has a private I/O server as its group leader. (For a description of the group leader concept, see - <seealso marker="erts:index"><c>ERTS</c></seealso>). + <seealso marker="erts:index">ERTS</seealso>). The central I/O server process, which handles the output from regular test cases and configuration functions, does not respond to I/O messages during execution of parallel groups. This is important to understand @@ -1032,8 +1032,8 @@ 6. Categorized error, importance = 99</pre> <p>The arguments <c>Format</c> and <c>FormatArgs</c> in <c>ct:log/print/pal</c> are - always passed on to the <c>stdlib</c> function <c>io:format/3</c> (For details, - see the <seealso marker="stdlib:io"><c>stdlib:io</c></seealso> manual page).</p> + always passed on to the STDLIB function <c>io:format/3</c> (For details, + see the <seealso marker="stdlib:io"><c>io</c></seealso> manual page).</p> <p><c>ct:pal/4</c> and <c>ct:log/5</c> add headers to strings being printed to the log file. The strings are also wrapped in div tags with a CSS class diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index d7ae81a5ce..f9f845e1a9 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -68,6 +68,7 @@ log/1, log/2, log/3, log/4, log/5, print/1, print/2, print/3, print/4, pal/1, pal/2, pal/3, pal/4, + set_verbosity/2, get_verbosity/1, capture_start/0, capture_stop/0, capture_get/0, capture_get/1, fail/1, fail/2, comment/1, comment/2, make_priv_dir/0, testcases/2, userdata/2, userdata/3, @@ -715,6 +716,24 @@ pal(Category,Importance,Format,Args) -> ct_logs:tc_pal(Category,Importance,Format,Args). %%%----------------------------------------------------------------- +%%% @spec set_verbosity(Category, Level) -> ok +%%% Category = default | atom() +%%% Level = integer() +%%% +%%% @doc Set the verbosity level for a category +set_verbosity(Category, Level) -> + ct_util:set_verbosity({Category,Level}). + +%%%----------------------------------------------------------------- +%%% @spec get_verbosity(Category) -> Level | undefined +%%% Category = default | atom() +%%% Level = integer() +%%% +%%% @doc Read the verbosity level for a category +get_verbosity(Category) -> + ct_util:get_verbosity(Category). + +%%%----------------------------------------------------------------- %%% @spec capture_start() -> ok %%% %%% @doc Start capturing all text strings printed to stdout during diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl index 23ba1ab981..8b59d3ab23 100644 --- a/lib/common_test/src/ct_gen_conn.erl +++ b/lib/common_test/src/ct_gen_conn.erl @@ -27,7 +27,7 @@ -export([start/4, stop/1, get_conn_pid/1, check_opts/1]). -export([call/2, call/3, return/2, do_within_time/2]). --export([log/3, start_log/1, cont_log/2, end_log/0]). +-export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]). %%---------------------------------------------------------------------- %% Exported types @@ -175,6 +175,14 @@ cont_log(Format,Args) -> log(cont_log,[Format,Args]). %%%----------------------------------------------------------------- +%%% @spec cont_log_no_timestamp(Format,Args) -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:cont_log/2 +cont_log_no_timestamp(Format,Args) -> + log(cont_log_no_timestamp,[Format,Args]). + +%%%----------------------------------------------------------------- %%% @spec end_log() -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 53245c596a..9282a9f81d 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -32,7 +32,7 @@ -export([init/2, close/2, init_tc/1, end_tc/1]). -export([register_groupleader/2, unregister_groupleader/1]). -export([get_log_dir/0, get_log_dir/1]). --export([log/3, start_log/1, cont_log/2, end_log/0]). +-export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]). -export([set_stylesheet/2, clear_stylesheet/1]). -export([add_external_logs/1, add_link/3]). -export([make_last_run_index/0]). @@ -373,6 +373,20 @@ cont_log(Format,Args) -> ok. %%%----------------------------------------------------------------- +%%% @spec cont_log_no_timestamp(Format,Args) -> ok +%%% +%%% @doc Adds information about an activity (tool-internal use only). +%%% +%%% @see start_log/1 +%%% @see end_log/0 +cont_log_no_timestamp([],[]) -> + ok; +cont_log_no_timestamp(Format,Args) -> + cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, + [{Format,Args}],true}), + ok. + +%%%----------------------------------------------------------------- %%% @spec end_log() -> ok %%% %%% @doc Ends the logging of an activity (tool-internal use only). @@ -595,7 +609,6 @@ div_header(Class,Printer) -> div_footer() -> "</pre></div>\n<pre>". - maybe_log_timestamp() -> {MS,S,US} = ?now, case get(log_timestamp) of diff --git a/lib/common_test/src/ct_make.erl b/lib/common_test/src/ct_make.erl index e7a9cfa843..f22959d457 100644 --- a/lib/common_test/src/ct_make.erl +++ b/lib/common_test/src/ct_make.erl @@ -342,5 +342,7 @@ check_includes2(Epp, File, ObjMTime) -> epp:close(Epp), false; {error, _Error} -> + check_includes2(Epp, File, ObjMTime); + {warning, _Warning} -> check_includes2(Epp, File, ObjMTime) end. diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index 8fb411ec4f..34d27ed5f4 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -954,7 +954,7 @@ log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port}, true -> ok; false -> - ct_gen_conn:cont_log(String,Args) + ct_gen_conn:cont_log_no_timestamp(String,Args) end; ForcePrint == true -> @@ -965,7 +965,7 @@ log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port}, %% called ct_gen_conn:log(heading(Action,Name1),String,Args); false -> - ct_gen_conn:cont_log(String,Args) + ct_gen_conn:cont_log_no_timestamp(String,Args) end end end. @@ -1224,7 +1224,6 @@ teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO, EOMod = if TotalTO /= infinity -> EO#eo{total_timeout=trunc(TotalTO)}; true -> EO end, - ExpectFun = case EOMod#eo.seq of true -> fun() -> seq_expect(Name,Pid,Data,Pattern,Acc,EOMod) @@ -1247,38 +1246,34 @@ teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO, true -> IdleTO end, + {PatOrPats1,Acc1,Rest1} = case NotFinished of + {nomatch,Rest0} -> + %% one expect + {Pattern,[],Rest0}; + {continue,Pats0,Acc0,Rest0} -> + %% sequence + {Pats0,Acc0,Rest0} + end, case timer:tc(ct_gen_conn, do_within_time, [Fun,BreakAfter]) of - {_,{error,Reason}} -> + {_,{error,Reason}} -> %% A timeout will occur when the telnet connection %% is idle for EO#eo.idle_timeout milliseconds. + if Rest1 /= [] -> + log(name_or_pid(Name,Pid)," ~ts",[Rest1]); + true -> + ok + end, {error,Reason}; {_,{ok,Data1}} when TotalTO == infinity -> - case NotFinished of - {nomatch,Rest} -> - %% One expect - teln_expect1(Name,Pid,Rest++Data1, - Pattern,[],EOMod); - {continue,Patterns1,Acc1,Rest} -> - %% Sequence - teln_expect1(Name,Pid,Rest++Data1, - Patterns1,Acc1,EOMod) - end; + teln_expect1(Name,Pid,Rest1++Data1,PatOrPats1,Acc1,EOMod); {Elapsed,{ok,Data1}} -> TVal = TotalTO - (Elapsed/1000), if TVal =< 0 -> {error,timeout}; true -> EO1 = EO#eo{total_timeout = TVal}, - case NotFinished of - {nomatch,Rest} -> - %% One expect - teln_expect1(Name,Pid,Rest++Data1, - Pattern,[],EO1); - {continue,Patterns1,Acc1,Rest} -> - %% Sequence - teln_expect1(Name,Pid,Rest++Data1, - Patterns1,Acc1,EO1) - end + teln_expect1(Name,Pid,Rest1++Data1, + PatOrPats1,Acc1,EO1) end end end. @@ -1416,14 +1411,14 @@ match_lines(Name,Pid,Data,Patterns,EO) -> case one_line(Data,[]) of {noline,Rest} when FoundPrompt=/=false -> %% This is the line including the prompt - case match_line(Name,Pid,Rest,Patterns,FoundPrompt,EO) of + case match_line(Name,Pid,Rest,Patterns,FoundPrompt,false,EO) of nomatch -> {nomatch,prompt}; {Tag,Match} -> {Tag,Match,[]} end; {noline,Rest} when EO#eo.prompt_check==false -> - case match_line(Name,Pid,Rest,Patterns,false,EO) of + case match_line(Name,Pid,Rest,Patterns,false,false,EO) of nomatch -> {nomatch,Rest}; {Tag,Match} -> @@ -1432,7 +1427,7 @@ match_lines(Name,Pid,Data,Patterns,EO) -> {noline,Rest} -> {nomatch,Rest}; {Line,Rest} -> - case match_line(Name,Pid,Line,Patterns,false,EO) of + case match_line(Name,Pid,Line,Patterns,false,true,EO) of nomatch -> match_lines(Name,Pid,Rest,Patterns,EO); {Tag,Match} -> @@ -1440,45 +1435,50 @@ match_lines(Name,Pid,Data,Patterns,EO) -> end end. - %% For one line, match each pattern -match_line(Name,Pid,Line,Patterns,FoundPrompt,EO) -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,match). +match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO) -> + match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO,match). -match_line(Name,Pid,Line,[prompt|Patterns],false,EO,RetTag) -> - match_line(Name,Pid,Line,Patterns,false,EO,RetTag); -match_line(Name,Pid,Line,[prompt|_Patterns],FoundPrompt,_EO,RetTag) -> +match_line(Name,Pid,Line,[prompt|Patterns],false,Term,EO,RetTag) -> + match_line(Name,Pid,Line,Patterns,false,Term,EO,RetTag); +match_line(Name,Pid,Line,[prompt|_Patterns],FoundPrompt,_Term,_EO,RetTag) -> log(name_or_pid(Name,Pid)," ~ts",[Line]), log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), {RetTag,{prompt,FoundPrompt}}; -match_line(Name,Pid,Line,[{prompt,PromptType}|_Patterns],FoundPrompt,_EO,RetTag) - when PromptType==FoundPrompt -> +match_line(Name,Pid,Line,[{prompt,PromptType}|_Patterns],FoundPrompt,_Term, + _EO,RetTag) when PromptType==FoundPrompt -> log(name_or_pid(Name,Pid)," ~ts",[Line]), log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), {RetTag,{prompt,FoundPrompt}}; -match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,EO,RetTag) +match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,Term, + EO,RetTag) when PromptType=/=FoundPrompt -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); -match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,EO,RetTag) -> + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); +match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,Term,EO,RetTag) -> case re:run(Line,Pattern,[{capture,all,list}]) of nomatch -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); {match,Match} -> log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), {RetTag,{Tag,Match}} end; -match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,EO,RetTag) -> +match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) -> case re:run(Line,Pattern,[{capture,all,list}]) of nomatch -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); {match,Match} -> log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), {RetTag,Match} end; -match_line(Name,Pid,Line,[],FoundPrompt,EO,match) -> - match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,EO,halt); -match_line(Name,Pid,Line,[],_FoundPrompt,_EO,halt) -> +match_line(Name,Pid,Line,[],FoundPrompt,Term,EO,match) -> + match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,Term,EO,halt); +%% print any terminated line that can not be matched +match_line(Name,Pid,Line,[],_FoundPrompt,true,_EO,halt) -> log(name_or_pid(Name,Pid)," ~ts",[Line]), + nomatch; +%% if there's no line termination, Line is saved as Rest (above) and will +%% be printed later +match_line(_Name,_Pid,_Line,[],_FoundPrompt,false,_EO,halt) -> nomatch. one_line([$\n|Rest],Line) -> diff --git a/lib/common_test/src/erl2html2.erl b/lib/common_test/src/erl2html2.erl index e819f345de..7f3eb699de 100644 --- a/lib/common_test/src/erl2html2.erl +++ b/lib/common_test/src/erl2html2.erl @@ -130,6 +130,8 @@ parse_preprocessed_file(Epp, File, InCorrectFile) -> throw({error,Reason,InCorrectFile}); {error,_Reason} -> parse_preprocessed_file(Epp, File, InCorrectFile); + {warning,_} -> + parse_preprocessed_file(Epp, File, InCorrectFile); {eof,_Location} -> [] end. diff --git a/lib/common_test/test/ct_auto_compile_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE.erl index e6939d23c0..dface99b8f 100644 --- a/lib/common_test/test/ct_auto_compile_SUITE.erl +++ b/lib/common_test/test/ct_auto_compile_SUITE.erl @@ -27,6 +27,7 @@ %%% The suites used for the test are located in the data directory. %%%------------------------------------------------------------------- -module(ct_auto_compile_SUITE). +-warning("Ignore me -- testing that the debugger can handle warnings"). -compile(export_all). diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 9879e0f20d..cbbfe408a8 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -113,10 +113,14 @@ userconfig_static(Config) when is_list(Config) -> ["config_static_SUITE"]). userconfig_dynamic(Config) when is_list(Config) -> - run_test(config_dynamic_SUITE, - Config, - {userconfig, {config_driver, "config_server"}}, - ["config_dynamic_SUITE"]). + case skip_dynamic() of + true -> {skip,"TimeWarpingOS"}; + false -> + run_test(config_dynamic_SUITE, + Config, + {userconfig, {config_driver, "config_server"}}, + ["config_dynamic_SUITE"]) + end. testspec_legacy(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), @@ -147,16 +151,20 @@ testspec_static(Config) when is_list(Config) -> file:delete(filename:join(ConfigDir, "spec_static.spec")). testspec_dynamic(Config) when is_list(Config) -> - DataDir = ?config(data_dir, Config), - ConfigDir = ?config(config_dir, Config), - make_spec(DataDir, ConfigDir, "spec_dynamic.spec", - [config_dynamic_SUITE], - [{userconfig, {config_driver, "config_server"}}]), - run_test(config_dynamic_SUITE, - Config, - {spec, filename:join(ConfigDir, "spec_dynamic.spec")}, - []), - file:delete(filename:join(ConfigDir, "spec_dynamic.spec")). + case skip_dynamic() of + true -> {skip,"TimeWarpingOS"}; + false -> + DataDir = ?config(data_dir, Config), + ConfigDir = ?config(config_dir, Config), + make_spec(DataDir, ConfigDir, "spec_dynamic.spec", + [config_dynamic_SUITE], + [{userconfig, {config_driver, "config_server"}}]), + run_test(config_dynamic_SUITE, + Config, + {spec, filename:join(ConfigDir, "spec_dynamic.spec")}, + []), + file:delete(filename:join(ConfigDir, "spec_dynamic.spec")) + end. @@ -198,6 +206,23 @@ setup_env(Test, Config, CTConfig) -> reformat_events(Events, EH) -> ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% Test related to 'localtime' will often fail if the test host is +%%% time warping, so let's just skip the 'dynamic' tests then. +skip_dynamic() -> + case os:getenv("TS_EXTRA_PLATFORM_LABEL") of + TSExtraPlatformLabel when is_list(TSExtraPlatformLabel) -> + case string:str(TSExtraPlatformLabel,"TimeWarpingOS") of + 0 -> false; + _ -> true + end; + _ -> + false + end. + + + %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl index a65275da43..f2580ad8e9 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl @@ -83,7 +83,9 @@ end_per_suite(Config) -> %% Running the netconf server in a remote node, test that the client %% process terminates if the remote node goes down. remote_crash(Config) -> - {ok,Node} = ct_slave:start(nc_remote_crash), + {ok,Node} = ct_slave:start(nc_remote_crash,[{boot_timeout,15}, + {init_timeout,15}, + {startup_timeout,15}]), Pa = filename:dirname(code:which(?NS)), true = rpc:call(Node,code,add_patha,[Pa]), rpc:call(Node,code,load_file,[crypto]), diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl index e62bc617fa..2412ea6aba 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl @@ -302,10 +302,14 @@ table_trans(Fun,Args) -> S -> apply(Fun,Args); Pid -> + Ref = erlang:monitor(process,Pid), Pid ! {table_trans,Fun,Args,self()}, receive {table_trans_done,Result} -> - Result + erlang:demonitor(Ref,[flush]), + Result; + {'DOWN',Ref,process,Pid,Reason} -> + exit({main_ns_proc_died,Reason}) after 20000 -> exit(table_trans_timeout) end diff --git a/lib/common_test/test_server/ts_run.erl b/lib/common_test/test_server/ts_run.erl index 66db1ff9a7..82ae44ec06 100644 --- a/lib/common_test/test_server/ts_run.erl +++ b/lib/common_test/test_server/ts_run.erl @@ -258,7 +258,7 @@ make_command(Vars, Spec, State) -> run_batch(Vars, _Spec, State) -> process_flag(trap_exit, true), - Command = State#state.command ++ " -noinput -s erlang halt", + Command = State#state.command ++ " -noinput -eval \"erlang:halt(0,[{flush,false}]).\"", ts_lib:progress(Vars, 1, "Command: ~ts~n", [Command]), io:format(user, "Command: ~ts~n",[Command]), Port = open_port({spawn, Command}, [stream, in, eof, exit_status]), diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 61e214294e..3ce37b98e9 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -136,7 +136,7 @@ (see <seealso marker="erts:absform">The Abstract Format</seealso> in ERTS User's Guide) in the compiled beam module. Tools - such as <c>Debugger</c>, <c>Xref</c>, and <c>Cover</c> require + such as Debugger, Xref, and Cover require the debug information to be included.</p> <p><em>Warning</em>: Source code can be reconstructed from @@ -544,7 +544,7 @@ module.beam: module.erl \ compiler to be deprecated. Notice that the compiler does not know about attribute <c>-deprecated()</c>, but uses an assembled list of deprecated functions in Erlang/OTP. To - do a more general check, the <c>Xref</c> tool can be used. + do a more general check, the Xref tool can be used. See also <seealso marker="tools:xref#deprecated_function">xref(3)</seealso> and the function @@ -846,7 +846,7 @@ pi() -> 3.1416. <section> <title>Inlining of List Functions</title> <p>The compiler can also inline various list manipulation functions - from the module <c>list</c> in <c>STDLIB</c>.</p> + from the module <c>list</c> in STDLIB.</p> <p>This feature must be explicitly enabled with a compiler option or a <c>-compile()</c> attribute in the source module.</p> diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index 92b3d809fc..dd6b132a92 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -280,7 +280,7 @@ <item> <p> The <c>cerl</c> and <c>cerl_trees</c> modules in the - <c>compiler</c> application are now documented.</p> + Compiler application are now documented.</p> <p> Own Id: OTP-11978</p> </item> @@ -1982,7 +1982,7 @@ <c>RightExpr</c> or vice versa. The evaluation order is only important if the expressions contains and/or depends on operations with side-effects, such as message passing - or <c>ets</c> operations.</p> + or ETS operations.</p> <p> Own Id: OTP-7206</p> </item> diff --git a/lib/compiler/doc/src/ref_man.xml b/lib/compiler/doc/src/ref_man.xml index f5466553c0..c32c499008 100644 --- a/lib/compiler/doc/src/ref_man.xml +++ b/lib/compiler/doc/src/ref_man.xml @@ -30,7 +30,7 @@ <file>application.sgml</file> </header> <description> - <p>The <c>Compiler</c> application compiles Erlang + <p>The Compiler application compiles Erlang code to byte-code. The highly compact byte-code is executed by the Erlang emulator.</p> </description> diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl index 85d332c56e..ec41925beb 100644 --- a/lib/compiler/src/beam_block.erl +++ b/lib/compiler/src/beam_block.erl @@ -159,14 +159,43 @@ find_fixpoint(OptFun, Is0) -> end. %% move_allocates(Is0) -> Is -%% Move allocate instructions upwards in the instruction stream, in the -%% hope of getting more possibilities for optimizing away moves later. +%% Move allocate instructions upwards in the instruction stream +%% (within the same block), in the hope of getting more possibilities +%% for optimizing away moves later. %% -%% NOTE: Moving allocation instructions is only safe because it is done -%% immediately after code generation so that we KNOW that if {x,X} is -%% initialized, all x registers with lower numbers are also initialized. -%% That assumption may not be true after other optimizations, such as -%% the beam_utils:live_opt/1 optimization. +%% For example, we can transform the following instructions: +%% +%% get_tuple_element x(1) Element => x(2) +%% allocate_zero StackSize 3 %% x(0), x(1), x(2) are live +%% +%% to the following instructions: +%% +%% allocate_zero StackSize 2 %% x(0) and x(1) are live +%% get_tuple_element x(1) Element => x(2) +%% +%% NOTE: Since the beam_reorder pass has been run, it is no longer +%% safe to assume that if x(N) is initialized, then all lower-numbered +%% x registers are also initialized. +%% +%% For example, in general it is not safe to transform the following +%% instructions: +%% +%% get_tuple_element x(0) Element => x(1) +%% allocate_zero StackSize 3 %x(0), x(1), x(2) are live +%% +%% to the following instructions: +%% +%% allocate_zero StackSize 3 +%% get_tuple_element x(0) Element => x(1) +%% +%% The transformation is safe if and only if x(1) has been +%% initialized previously. Unfortunately, beam_reorder may have moved +%% a get_tuple_element instruction so that x(1) is not always +%% initialized when this code is reached. To find whether or not x(1) +%% is initialized, we would need to analyze all code preceding these +%% two instructions (across branches). Since we currently don't have +%% any practical mechanism for doing that, we will have to +%% conservatively assume that the transformation is unsafe. move_allocates([{block,Bl0}|Is]) -> Bl = move_allocates_1(reverse(Bl0), []), @@ -175,27 +204,19 @@ move_allocates([I|Is]) -> [I|move_allocates(Is)]; move_allocates([]) -> []. -move_allocates_1([{set,[],[],{alloc,_,_}=Alloc}|Is0], Acc0) -> - {Is,Acc} = move_allocates_2(Alloc, Is0, Acc0), - move_allocates_1(Is, Acc); +move_allocates_1([I|Is], [{set,[],[],{alloc,Live0,Info}}|Acc]=Acc0) -> + case {alloc_may_pass(I),alloc_live_regs(I, Live0)} of + {false,_} -> + move_allocates_1(Is, [I|Acc0]); + {true,not_possible} -> + move_allocates_1(Is, [I|Acc0]); + {true,Live} when is_integer(Live) -> + A = {set,[],[],{alloc,Live,Info}}, + move_allocates_1(Is, [A,I|Acc]) + end; move_allocates_1([I|Is], Acc) -> move_allocates_1(Is, [I|Acc]); -move_allocates_1([], Is) -> Is. - -move_allocates_2({alloc,Live,Info}, [{set,[],[],{alloc,Live0,Info0}}|Is], Acc) -> - Live = Live0, % Assertion. - Alloc = {alloc,Live,combine_alloc(Info0, Info)}, - move_allocates_2(Alloc, Is, Acc); -move_allocates_2({alloc,Live,Info}=Alloc0, [I|Is]=Is0, Acc) -> - case alloc_may_pass(I) of - false -> - {Is0,[{set,[],[],Alloc0}|Acc]}; - true -> - Alloc = {alloc,alloc_live_regs(I, Live),Info}, - move_allocates_2(Alloc, Is, [I|Acc]) - end; -move_allocates_2(Alloc, [], Acc) -> - {[],[{set,[],[],Alloc}|Acc]}. +move_allocates_1([], Acc) -> Acc. alloc_may_pass({set,_,_,{alloc,_,_}}) -> false; alloc_may_pass({set,_,_,{set_tuple_element,_}}) -> false; @@ -204,9 +225,6 @@ alloc_may_pass({set,_,_,put_list}) -> false; alloc_may_pass({set,_,_,put}) -> false; alloc_may_pass({set,_,_,_}) -> true. -combine_alloc({_,Ns,Nh1,Init}, {_,nostack,Nh2,[]}) -> - {zero,Ns,beam_utils:combine_heap_needs(Nh1, Nh2),Init}. - %% opt([Instruction]) -> [Instruction] %% Optimize the instruction stream inside a basic block. @@ -393,10 +411,19 @@ eliminate_use_of_from_reg([I]=Is, From, _To, Acc) -> %% opt_alloc(Instructions) -> Instructions' %% Optimises all allocate instructions. +opt_alloc([{set,[],[],{alloc,Live0,Info0}}, + {set,[],[],{alloc,Live,Info}}|Is]) -> + Live = Live0, %Assertion. + Alloc = combine_alloc(Info0, Info), + I = {set,[],[],{alloc,Live,Alloc}}, + opt_alloc([I|Is]); opt_alloc([{set,[],[],{alloc,R,{_,Ns,Nh,[]}}}|Is]) -> [{set,[],[],opt_alloc(Is, Ns, Nh, R)}|Is]; opt_alloc([I|Is]) -> [I|opt_alloc(Is)]; opt_alloc([]) -> []. + +combine_alloc({_,Ns,Nh1,Init}, {_,nostack,Nh2,[]}) -> + {zero,Ns,beam_utils:combine_heap_needs(Nh1, Nh2),Init}. %% opt_alloc(Instructions, FrameSize, HeapNeed, LivingRegs) -> [Instr] %% Generates the optimal sequence of instructions for @@ -445,13 +472,14 @@ count_ones(Bits, Acc) -> alloc_live_regs({set,Ds,Ss,_}, Regs0) -> Rset = x_live(Ss, x_dead(Ds, (1 bsl Regs0)-1)), - live_regs(Rset). + live_regs(0, Rset). -live_regs(Regs) -> - live_regs_1(0, Regs). - -live_regs_1(N, 0) -> N; -live_regs_1(N, Regs) -> live_regs_1(N+1, Regs bsr 1). +live_regs(N, 0) -> + N; +live_regs(N, Regs) when Regs band 1 =:= 1 -> + live_regs(N+1, Regs bsr 1); +live_regs(_, _) -> + not_possible. x_dead([{x,N}|Rs], Regs) -> x_dead(Rs, Regs band (bnot (1 bsl N))); x_dead([_|Rs], Regs) -> x_dead(Rs, Regs); diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 09cd3aa2d4..48b5a32814 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -167,12 +167,18 @@ share_1([{label,L}=Lbl|Is], Dict0, Seq, Acc) -> end; share_1([{func_info,_,_,_}=I|Is], _, [], Acc) -> reverse(Is, [I|Acc]); +share_1([{'catch',_,_}=I|Is], Dict0, Seq, Acc) -> + Dict = clean_non_sharable(Dict0), + share_1(Is, Dict, [I|Seq], Acc); share_1([{'try',_,_}=I|Is], Dict0, Seq, Acc) -> Dict = clean_non_sharable(Dict0), share_1(Is, Dict, [I|Seq], Acc); share_1([{try_case,_}=I|Is], Dict0, Seq, Acc) -> Dict = clean_non_sharable(Dict0), share_1(Is, Dict, [I|Seq], Acc); +share_1([{catch_end,_}=I|Is], Dict0, Seq, Acc) -> + Dict = clean_non_sharable(Dict0), + share_1(Is, Dict, [I|Seq], Acc); share_1([I|Is], Dict, Seq, Acc) -> case is_unreachable_after(I) of false -> @@ -182,18 +188,18 @@ share_1([I|Is], Dict, Seq, Acc) -> end. clean_non_sharable(Dict) -> - %% We are passing in or out of a 'try' block. Remove - %% sequences that should not shared over the boundaries - %% of a 'try' block. Since the end of the sequence must match, - %% the only possible match between a sequence outside and - %% a sequence inside the 'try' block is a sequence that ends - %% with an instruction that causes an exception. Any sequence - %% that causes an exception must contain a line/1 instruction. + %% We are passing in or out of a 'catch' or 'try' block. Remove + %% sequences that should not be shared over the boundaries of the + %% block. Since the end of the sequence must match, the only + %% possible match between a sequence outside and a sequence inside + %% the 'catch'/'try' block is a sequence that ends with an + %% instruction that causes an exception. Any sequence that causes + %% an exception must contain a line/1 instruction. maps:filter(fun(K, _V) -> sharable_with_try(K) end, Dict). sharable_with_try([{line,_}|_]) -> %% This sequence may cause an exception and may potentially - %% match a sequence on the other side of the 'try' block + %% match a sequence on the other side of the 'catch'/'try' block %% boundary. false; sharable_with_try([_|Is]) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 4c0cb6780a..16dba35adc 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -808,9 +808,11 @@ validate_bs_skip_utf(Fail, Ctx, Live, Vst0) -> %% A possibility for garbage collection must not occur between setelement/3 and %% set_tuple_element/3. %% +%% Note that #vst.current will be 'none' if the instruction is unreachable. +%% val_dsetel({move,_,_}, Vst) -> Vst; -val_dsetel({call_ext,3,{extfunc,erlang,setelement,3}}, #vst{current=St}=Vst) -> +val_dsetel({call_ext,3,{extfunc,erlang,setelement,3}}, #vst{current=#st{}=St}=Vst) -> Vst#vst{current=St#st{setelem=true}}; val_dsetel({set_tuple_element,_,_,_}, #vst{current=#st{setelem=false}}) -> error(illegal_context_for_set_tuple_element); diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 82ff8a95f3..e951a25e04 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -43,6 +43,10 @@ -type abstract_code() :: [erl_parse:abstract_form()]. +%% Internal representations used for 'from_asm' and 'from_beam' compilation can +%% also be valid, but have no relevant types defined. +-type forms() :: abstract_code() | cerl:c_module(). + -type option() :: atom() | {atom(), term()} | {'d', atom(), term()}. -type err_info() :: {erl_anno:line() | 'none', @@ -88,7 +92,7 @@ file(File, Opt) -> forms(Forms) -> forms(Forms, ?DEFAULT_OPTIONS). --spec forms(abstract_code(), [option()] | option()) -> comp_ret(). +-spec forms(forms(), [option()] | option()) -> comp_ret(). forms(Forms, Opts) when is_list(Opts) -> do_compile({forms,Forms}, [binary|Opts++env_default_opts()]); @@ -116,7 +120,7 @@ noenv_file(File, Opts) when is_list(Opts) -> noenv_file(File, Opt) -> noenv_file(File, [Opt|?DEFAULT_OPTIONS]). --spec noenv_forms(abstract_code(), [option()] | option()) -> comp_ret(). +-spec noenv_forms(forms(), [option()] | option()) -> comp_ret(). noenv_forms(Forms, Opts) when is_list(Opts) -> do_compile({forms,Forms}, [binary|Opts]); @@ -236,6 +240,8 @@ format_error({epp,E}) -> epp:format_error(E); format_error(write_error) -> "error writing file"; +format_error({write_error, Error}) -> + io_lib:format("error writing file: ~ts", [file:format_error(Error)]); format_error({rename,From,To,Error}) -> io_lib:format("failed to rename ~ts to ~ts: ~ts", [From,To,file:format_error(Error)]); @@ -1206,7 +1212,7 @@ makedep_output(#compile{code=Code,options=Opts,ofile=Ofile}=St) -> end, {ok,St} catch - exit:_ -> + error:_ -> %% Couldn't write to output Makefile. Err = {St#compile.ifile,[{none,?MODULE,write_error}]}, {error,St#compile{errors=St#compile.errors++[Err]}} @@ -1479,8 +1485,8 @@ save_binary_1(St) -> end, {error,St#compile{errors=St#compile.errors ++ Es}} end; - {error,_Error} -> - Es = [{Tfile,[{none,compile,write_error}]}], + {error,Error} -> + Es = [{Tfile,[{none,compile,{write_error,Error}}]}], {error,St#compile{errors=St#compile.errors ++ Es}} end. @@ -1628,8 +1634,8 @@ listing(LFun, Ext, St) -> LFun(Lf, Code), ok = file:close(Lf), {ok,St}; - {error,_Error} -> - Es = [{Lfile,[{none,compile,write_error}]}], + {error,Error} -> + Es = [{Lfile,[{none,compile,{write_error,Error}}]}], {error,St#compile{errors=St#compile.errors ++ Es}} end. diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index e0de50f3ae..08b02101a6 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -468,7 +468,8 @@ bitstr(#c_bitstr{val=Val,size=Size}=BinSeg, Sub) -> %% Currently, we don't attempt to check binaries because they %% are difficult to check. -is_safe_simple(#c_var{}, _) -> true; +is_safe_simple(#c_var{}=Var, _) -> + not cerl:is_c_fname(Var); is_safe_simple(#c_cons{hd=H,tl=T}, Sub) -> is_safe_simple(H, Sub) andalso is_safe_simple(T, Sub); is_safe_simple(#c_tuple{es=Es}, Sub) -> is_safe_simple_list(Es, Sub); diff --git a/lib/compiler/test/beam_block_SUITE.erl b/lib/compiler/test/beam_block_SUITE.erl index 4bcb252833..9fcb6e497d 100644 --- a/lib/compiler/test/beam_block_SUITE.erl +++ b/lib/compiler/test/beam_block_SUITE.erl @@ -21,7 +21,8 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, - get_map_elements/1,otp_7345/1,move_opt_across_gc_bif/1]). + get_map_elements/1,otp_7345/1,move_opt_across_gc_bif/1, + erl_202/1]). %% The only test for the following functions is that %% the code compiles and is accepted by beam_validator. @@ -37,7 +38,8 @@ groups() -> [{p,[parallel], [get_map_elements, otp_7345, - move_opt_across_gc_bif + move_opt_across_gc_bif, + erl_202 ]}]. init_per_suite(Config) -> @@ -135,6 +137,27 @@ positive(speaking) -> paris([], P) -> P + 1. + +%% See https://bugs.erlang.org/browse/ERL-202. +%% Test that move_allocates/1 in beam_block doesn't move allocate +%% when it would not be safe. + +-record(erl_202_r1, {y}). +-record(erl_202_r2, {x}). + +erl_202(_Config) -> + Ref = make_ref(), + Ref = erl_202({{1,2},Ref}, 42), + + {Ref} = erl_202({7,8}, #erl_202_r1{y=#erl_202_r2{x=Ref}}), + + ok. + +erl_202({{_, _},X}, _) -> + X; +erl_202({_, _}, #erl_202_r1{y=R2}) -> + {R2#erl_202_r2.x}. + %%% %%% The only test of the following code is that it compiles. %%% diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl index 0b13adaff2..088f63606c 100644 --- a/lib/compiler/test/beam_jump_SUITE.erl +++ b/lib/compiler/test/beam_jump_SUITE.erl @@ -21,7 +21,7 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, - undefined_label/1]). + undefined_label/1,ambiguous_catch_try_state/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -32,7 +32,8 @@ all() -> groups() -> [{p,[parallel], - [undefined_label + [undefined_label, + ambiguous_catch_try_state ]}]. init_per_suite(Config) -> @@ -57,3 +58,17 @@ flights(0, [], []) when [], 0; 0.0, [], false -> clark; flights(_, Reproduction, introduction) when false, Reproduction -> responsible. + +%% [ERL-209] beam_jump would share 'catch' blocks, causing an +%% ambiguous_catch_try_state error in beam_validator. + +ambiguous_catch_try_state(_Config) -> + {{'EXIT',{{case_clause,song},_}},{'EXIT',{{case_clause,song},_}}} = + checks(42), + ok. + +river() -> song. + +checks(Wanted) -> + %% Must be one line to cause the unsafe optimization. + {catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}. diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 263fd2ca7e..ca85eef688 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -32,7 +32,8 @@ bad_bin_match/1,bad_dsetel/1, state_after_fault_in_catch/1,no_exception_in_catch/1, undef_label/1,illegal_instruction/1,failing_gc_guard_bif/1, - map_field_lists/1,cover_bin_opt/1]). + map_field_lists/1,cover_bin_opt/1, + val_dsetel/1]). -include_lib("common_test/include/ct.hrl"). @@ -60,7 +61,7 @@ groups() -> freg_state,bad_bin_match,bad_dsetel, state_after_fault_in_catch,no_exception_in_catch, undef_label,illegal_instruction,failing_gc_guard_bif, - map_field_lists,cover_bin_opt]}]. + map_field_lists,cover_bin_opt,val_dsetel]}]. init_per_suite(Config) -> Config. @@ -546,3 +547,23 @@ beam_val(M) -> _ = [io:put_chars(beam_validator:format_error(E)) || E <- Errors], Errors. + +%%%------------------------------------------------------------------------- + +val_dsetel(_Config) -> + self() ! 13, + {'EXIT',{{try_clause,participating},_}} = (catch night(0)), + ok. + +night(Turned) -> + receive + 13 -> + try participating of engine -> 16 after false end + end, + %% The setelement/3 call is unreachable. + Turned(setelement(#{true => Turned}, + participating(Turned, "suit", 40, []), + Turned < Turned)), + ok. + +participating(_, _, _, _) -> ok. diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 376d2c8e9a..ced0e39d06 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -26,7 +26,7 @@ unused_multiple_values_error/1,unused_multiple_values/1, multiple_aliases/1,redundant_boolean_clauses/1, mixed_matching_clauses/1,unnecessary_building/1, - no_no_file/1]). + no_no_file/1,configuration/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -45,7 +45,7 @@ groups() -> unused_multiple_values_error,unused_multiple_values, multiple_aliases,redundant_boolean_clauses, mixed_matching_clauses,unnecessary_building, - no_no_file]}]. + no_no_file,configuration]}]. init_per_suite(Config) -> @@ -499,4 +499,16 @@ experiment() -> end, ok. + +%% Make sure we don't try to move a fun into a guard. +configuration(_Config) -> + {'EXIT',_} = (catch configuration()), + ok. + +configuration() -> + [forgotten || Components <- enemy, is_tuple(fun art/0)]. + +art() -> + creating. + id(I) -> I. diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 7183c395ae..00fc81c84f 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -37,7 +37,9 @@ #include <openssl/opensslconf.h> #include <openssl/crypto.h> +#ifndef OPENSSL_NO_DES #include <openssl/des.h> +#endif /* #ifndef OPENSSL_NO_DES */ /* #include <openssl/idea.h> This is not supported on the openssl OTP requires */ #include <openssl/dsa.h> #include <openssl/rsa.h> @@ -458,16 +460,29 @@ struct cipher_type_t { const size_t key_len; /* != 0 to also match on key_len */ }; +#ifdef OPENSSL_NO_DES +#define COND_NO_DES_PTR(Ptr) (NULL) +#else +#define COND_NO_DES_PTR(Ptr) (Ptr) +#endif + struct cipher_type_t cipher_types[] = { {{"rc2_cbc"}, {&EVP_rc2_cbc}}, - {{"des_cbc"}, {&EVP_des_cbc}}, - {{"des_cfb"}, {&EVP_des_cfb8}}, - {{"des_ecb"}, {&EVP_des_ecb}}, - {{"des_ede3_cbc"}, {&EVP_des_ede3_cbc}}, - {{"des_ede3_cbf"}, + {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}}, + {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}}, + {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}}, + {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}}, + {{"des_ede3_cbf"}, /* Misspelled, retained */ +#ifdef HAVE_DES_ede3_cfb_encrypt + {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)} +#else + {NULL} +#endif + }, + {{"des_ede3_cfb"}, #ifdef HAVE_DES_ede3_cfb_encrypt - {&EVP_des_ede3_cfb8} + {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)} #else {NULL} #endif @@ -749,7 +764,7 @@ static ERL_NIF_TERM algo_hash[8]; /* increase when extending the list */ static int algo_pubkey_cnt; static ERL_NIF_TERM algo_pubkey[7]; /* increase when extending the list */ static int algo_cipher_cnt; -static ERL_NIF_TERM algo_cipher[20]; /* increase when extending the list */ +static ERL_NIF_TERM algo_cipher[23]; /* increase when extending the list */ static void init_algorithms_types(ErlNifEnv* env) { @@ -785,10 +800,13 @@ static void init_algorithms_types(ErlNifEnv* env) algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp"); algo_cipher_cnt = 0; +#ifndef OPENSSL_NO_DES algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbc"); algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des_ede3"); #ifdef HAVE_DES_ede3_cfb_encrypt algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbf"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cfb"); +#endif #endif algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc"); algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc128"); @@ -800,8 +818,11 @@ static void init_algorithms_types(ErlNifEnv* env) #ifdef HAVE_AES_IGE algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ige256"); #endif +#ifndef OPENSSL_NO_DES algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cbc"); algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cfb"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_ecb"); +#endif algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cbc"); algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cfb64"); algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ofb64"); @@ -2141,7 +2162,7 @@ static ERL_NIF_TERM dss_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM DSA *dsa; int i; - if (!argv[0] == atom_sha + if (argv[0] != atom_sha || !enif_inspect_binary(env, argv[1], &digest_bin) || digest_bin.size != SHA_DIGEST_LENGTH || !enif_inspect_binary(env, argv[2], &sign_bin) @@ -2467,7 +2488,7 @@ static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar DSA* dsa; int i; - if (!argv[0] == atom_sha + if (argv[0] != atom_sha || !enif_inspect_binary(env, argv[1], &digest_bin) || digest_bin.size != SHA_DIGEST_LENGTH) { return enif_make_badarg(env); diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 5a5627747c..eda0f7af51 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -136,7 +136,7 @@ <code>stream_cipher() = rc4 | aes_ctr </code> <code>block_cipher() = aes_cbc | aes_cfb8 | aes_cfb128 | aes_ige256 | blowfish_cbc | - blowfish_cfb64 | des_cbc | des_cfb | des3_cbc | des3_cbf | des_ede3 | rc2_cbc </code> + blowfish_cfb64 | des_cbc | des_cfb | des3_cbc | des3_cfb | des_ede3 | rc2_cbc </code> <code>aead_cipher() = aes_gcm | chacha20_poly1305 </code> @@ -161,7 +161,7 @@ </p> <code> cipher_algorithms() = aes_cbc | aes_cfb8 | aes_cfb128 | aes_ctr | aes_gcm | aes_ige256 | blowfish_cbc | blowfish_cfb64 | chacha20_poly1305 | des_cbc | des_cfb | - des3_cbc | des3_cbf | des_ede3 | rc2_cbc | rc4 </code> + des3_cbc | des3_cfb | des_ede3 | rc2_cbc | rc4 </code> <code> public_key_algorithms() = rsa |dss | ecdsa | dh | ecdh | ec_gf2m</code> <p>Note that ec_gf2m is not strictly a public key algorithm, but a restriction on what curves are supported with ecdsa and ecdh. diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index 7d3a85326f..56e165a1d1 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -815,7 +815,7 @@ also been extended. </item><item> The <c>configure</c> scripts of <c>erl_interface</c> and <c>odbc</c> now search for thread libraries and thread library quirks the - same way as <c>erts</c> do. </item><item> The + same way as ERTS do. </item><item> The <c>configure</c> script of the <c>odbc</c> application now also looks for odbc libraries in <c>lib64</c> and <c>lib/64</c> directories when building on a 64-bit diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 025d57e9c5..da8626e38a 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -274,7 +274,7 @@ hmac_final_n(Context, HashLen) -> %% Ecrypt/decrypt %%% -spec block_encrypt(des_cbc | des_cfb | - des3_cbc | des3_cbf | des_ede3 | + des3_cbc | des3_cbf | des3_cfb | des_ede3 | blowfish_cbc | blowfish_cfb64 | blowfish_ofb64 | aes_cbc128 | aes_cfb8 | aes_cfb128 | aes_cbc256 | aes_ige256 | aes_cbc | @@ -301,6 +301,9 @@ block_encrypt(Type, Key0, Ivec, Data) when Type =:= des3_cbc; block_encrypt(des3_cbf, Key0, Ivec, Data) -> Key = check_des3_key(Key0), block_crypt_nif(des_ede3_cbf, Key, Ivec, Data, true); +block_encrypt(des3_cfb, Key0, Ivec, Data) -> + Key = check_des3_key(Key0), + block_crypt_nif(des_ede3_cfb, Key, Ivec, Data, true); block_encrypt(aes_ige256, Key, Ivec, Data) -> aes_ige_crypt_nif(Key, Ivec, Data, true); block_encrypt(aes_gcm, Key, Ivec, {AAD, Data}) -> @@ -311,7 +314,7 @@ block_encrypt(chacha20_poly1305, Key, Ivec, {AAD, Data}) -> chacha20_poly1305_encrypt(Key, Ivec, AAD, Data). -spec block_decrypt(des_cbc | des_cfb | - des3_cbc | des3_cbf | des_ede3 | + des3_cbc | des3_cbf | des3_cfb | des_ede3 | blowfish_cbc | blowfish_cfb64 | blowfish_ofb64 | aes_cbc128 | aes_cfb8 | aes_cfb128 | aes_cbc256 | aes_ige256 | aes_cbc | @@ -338,6 +341,9 @@ block_decrypt(Type, Key0, Ivec, Data) when Type =:= des3_cbc; block_decrypt(des3_cbf, Key0, Ivec, Data) -> Key = check_des3_key(Key0), block_crypt_nif(des_ede3_cbf, Key, Ivec, Data, false); +block_decrypt(des3_cfb, Key0, Ivec, Data) -> + Key = check_des3_key(Key0), + block_crypt_nif(des_ede3_cfb, Key, Ivec, Data, false); block_decrypt(aes_ige256, Key, Ivec, Data) -> notsup_to_error(aes_ige_crypt_nif(Key, Ivec, Data, false)); block_decrypt(aes_gcm, Key, Ivec, {AAD, Data, Tag}) -> @@ -857,10 +863,10 @@ des_ede3_cbc_decrypt(Key1, Key2, Key3, IVec, Data) -> binary(). des3_cfb_encrypt(Key1, Key2, Key3, IVec, Data) -> - block_encrypt(des3_cbf, [Key1, Key2, Key3], IVec, Data). + block_encrypt(des3_cfb, [Key1, Key2, Key3], IVec, Data). des3_cfb_decrypt(Key1, Key2, Key3, IVec, Data) -> - block_decrypt(des3_cbf, [Key1, Key2, Key3], IVec, Data). + block_decrypt(des3_cfb, [Key1, Key2, Key3], IVec, Data). %% %% Blowfish diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 6732f27824..7b07cef33f 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -50,6 +50,7 @@ all() -> {group, des_cfb}, {group, des3_cbc}, {group, des3_cbf}, + {group, des3_cfb}, {group, des_ede3}, {group, blowfish_cbc}, {group, blowfish_ecb}, @@ -94,6 +95,7 @@ groups() -> {des3_cbc,[], [block]}, {des_ede3,[], [block]}, {des3_cbf,[], [block]}, + {des3_cfb,[], [block]}, {rc2_cbc,[], [block]}, {aes_cbc128,[], [block]}, {aes_cfb8,[], [block]}, @@ -381,11 +383,8 @@ block_cipher({Type, Key, IV, PlainText, CipherText}) -> ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other1}}) end. -block_cipher_increment({Type, Key, IV, PlainTexts}) when Type == des_cbc; - Type == des3_cbc; - Type == aes_cbc; - Type == des_cbf - -> +block_cipher_increment({Type, Key, IV, PlainTexts}) + when Type == des_cbc; Type == aes_cbc; Type == des3_cbc -> block_cipher_increment(Type, Key, IV, IV, PlainTexts, iolist_to_binary(PlainTexts), []); block_cipher_increment({Type, Key, IV, PlainTexts, _CipherText}) when Type == aes_cbc -> Plain = iolist_to_binary(PlainTexts), @@ -582,6 +581,8 @@ do_block_iolistify({des3_cbc = Type, Key, IV, PlainText}) -> {Type, Key, IV, des_iolistify(PlainText)}; do_block_iolistify({des3_cbf = Type, Key, IV, PlainText}) -> {Type, Key, IV, des_iolistify(PlainText)}; +do_block_iolistify({des3_cfb = Type, Key, IV, PlainText}) -> + {Type, Key, IV, des_iolistify(PlainText)}; do_block_iolistify({des_ede3 = Type, Key, IV, PlainText}) -> {Type, Key, IV, des_iolistify(PlainText)}; do_block_iolistify({Type, Key, PlainText}) -> @@ -792,6 +793,9 @@ group_config(des3_cbc, Config) -> group_config(des3_cbf, Config) -> Block = des3_cbf(), [{block, Block} | Config]; +group_config(des3_cfb, Config) -> + Block = des3_cfb(), + [{block, Block} | Config]; group_config(des_ede3, Config) -> Block = des_ede3(), [{block, Block} | Config]; @@ -1193,7 +1197,16 @@ des_ede3() -> des3_cbf() -> [{des3_cbf, - [hexstr2bin("0123456789abcdef"), + [hexstr2bin("0123456789abcdef"), + hexstr2bin("fedcba9876543210"), + hexstr2bin("0f2d4b6987a5c3e1")], + hexstr2bin("1234567890abcdef"), + <<"Now is the time for all ">> + }]. + +des3_cfb() -> + [{des3_cfb, + [hexstr2bin("0123456789abcdef"), hexstr2bin("fedcba9876543210"), hexstr2bin("0f2d4b6987a5c3e1")], hexstr2bin("1234567890abcdef"), diff --git a/lib/crypto/test/old_crypto_SUITE.erl b/lib/crypto/test/old_crypto_SUITE.erl index 0d97290d10..4a6753b2ed 100644 --- a/lib/crypto/test/old_crypto_SUITE.erl +++ b/lib/crypto/test/old_crypto_SUITE.erl @@ -58,6 +58,7 @@ des_cfb_iter/1, des_ecb/1, des3_cbc/1, + des3_cbf/1, des3_cfb/1, rc2_cbc/1, aes_cfb/1, @@ -102,7 +103,7 @@ groups() -> hmac_rfc2202, hmac_rfc4231_sha224, hmac_rfc4231_sha256, hmac_rfc4231_sha384, hmac_rfc4231_sha512, des_cbc, aes_cfb, aes_cbc, - des_cfb, des_cfb_iter, des3_cbc, des3_cfb, rc2_cbc, + des_cfb, des_cfb_iter, des3_cbc, des3_cbf, des3_cfb, rc2_cbc, aes_cbc_iter, aes_ctr, aes_ctr_stream, des_cbc_iter, des_ecb, rand_uniform_test, strong_rand_test, rsa_verify_test, dsa_verify_test, rsa_sign_test, @@ -969,6 +970,9 @@ des_cbc(doc) -> des_cbc(suite) -> []; des_cbc(Config) when is_list(Config) -> + if_supported(des_cbc, fun des_cbc_do/0). + +des_cbc_do() -> ?line Key = hexstr2bin("0123456789abcdef"), ?line IVec = hexstr2bin("1234567890abcdef"), ?line Plain = "Now is the time for all ", @@ -992,6 +996,9 @@ des_cbc_iter(doc) -> des_cbc_iter(suite) -> []; des_cbc_iter(Config) when is_list(Config) -> + if_supported(des_cbc, fun des_cbc_iter_do/0). + +des_cbc_iter_do() -> ?line Key = hexstr2bin("0123456789abcdef"), ?line IVec = hexstr2bin("1234567890abcdef"), ?line Plain1 = "Now is the time ", @@ -1011,6 +1018,9 @@ des_cfb(doc) -> des_cfb(suite) -> []; des_cfb(Config) when is_list(Config) -> + if_supported(des_cfb, fun des_cfb_do/0). + +des_cfb_do() -> ?line Key = hexstr2bin("0123456789abcdef"), ?line IVec = hexstr2bin("1234567890abcdef"), ?line Plain = "Now is the", @@ -1027,6 +1037,9 @@ des_cfb_iter(doc) -> des_cfb_iter(suite) -> []; des_cfb_iter(Config) when is_list(Config) -> + if_supported(des_cfb, fun des_cfb_iter_do/0). + +des_cfb_iter_do() -> ?line Key = hexstr2bin("0123456789abcdef"), ?line IVec = hexstr2bin("1234567890abcdef"), ?line Plain1 = "Now i", @@ -1045,6 +1058,9 @@ des_ecb(doc) -> des_ecb(suite) -> []; des_ecb(Config) when is_list(Config) -> + if_supported(des_ecb, fun des_ecb_do/0). + +des_ecb_do() -> ?line Key = hexstr2bin("0123456789abcdef"), ?line Cipher1 = crypto:des_ecb_encrypt(Key, "Now is t"), ?line m(Cipher1, hexstr2bin("3fa40e8a984d4815")), @@ -1081,6 +1097,9 @@ des3_cbc(doc) -> des3_cbc(suite) -> []; des3_cbc(Config) when is_list(Config) -> + if_supported(des3_cbc, fun des3_cbc_do/0). + +des3_cbc_do() -> ?line Key1 = hexstr2bin("0123456789abcdef"), ?line Key2 = hexstr2bin("fedcba9876543210"), ?line Key3 = hexstr2bin("0f2d4b6987a5c3e1"), @@ -1112,6 +1131,19 @@ des3_cbc(Config) when is_list(Config) -> %% %% +des3_cbf(doc) -> + "Encrypt and decrypt according to CFB 3DES, and check the result."; +des3_cbf(suite) -> + []; +des3_cbf(Config) when is_list(Config) -> + case openssl_version() of + V when V < 16#90705F -> {skipped,"OpenSSL version too old"}; + _ -> + if_supported(des3_cbf, fun des3_cfb_do/0) + end. + +%% +%% des3_cfb(doc) -> "Encrypt and decrypt according to CFB 3DES, and check the result."; des3_cfb(suite) -> @@ -1119,7 +1151,8 @@ des3_cfb(suite) -> des3_cfb(Config) when is_list(Config) -> case openssl_version() of V when V < 16#90705F -> {skipped,"OpenSSL version too old"}; - _ -> des3_cfb_do() + _ -> + if_supported(des3_cfb, fun des3_cfb_do/0) end. des3_cfb_do() -> diff --git a/lib/debugger/doc/src/Makefile b/lib/debugger/doc/src/Makefile index 6c9617ca69..0f724b6f17 100644 --- a/lib/debugger/doc/src/Makefile +++ b/lib/debugger/doc/src/Makefile @@ -114,7 +114,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" diff --git a/lib/debugger/doc/src/i.xml b/lib/debugger/doc/src/i.xml index db89f23494..628b91e9e4 100644 --- a/lib/debugger/doc/src/i.xml +++ b/lib/debugger/doc/src/i.xml @@ -45,7 +45,7 @@ attached manually or automatically.</p> <p>By preference, these functions can be included in module - <seealso marker="stdlib:shell_default"><c>stdlib:shell_default</c></seealso>. + <seealso marker="stdlib:shell_default"><c>shell_default</c></seealso>. By default, they are included in that module.</p> </description> @@ -372,7 +372,7 @@ </fsummary> <desc> <p>Returns the current version number of the interpreter. - Same as the version number of the <c>Debugger</c> application.</p> + Same as the version number of the Debugger application.</p> </desc> </func> diff --git a/lib/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl index 3561454685..3e959e8e30 100644 --- a/lib/debugger/src/dbg_iserver.erl +++ b/lib/debugger/src/dbg_iserver.erl @@ -214,7 +214,6 @@ handle_call({new_process, Pid, Meta, Function}, _From, State) -> %% Code loading handle_call({load, Mod, Src, Bin}, _From, State) -> - %% Create an ETS table for storing information about the module Db = State#state.db, ModDb = ets:new(Mod, [ordered_set, public]), @@ -285,23 +284,28 @@ handle_call({contents, Mod, Pid}, _From, State) -> {reply, {ok, Bin}, State}; handle_call({raw_contents, Mod, Pid}, _From, State) -> Db = State#state.db, - [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}), - ModDb = if - Pid =:= any -> hd(ModDbs); - true -> - lists:foldl(fun(T, not_found) -> - [{T, Pids}] = ets:lookup(Db, T), - case lists:member(Pid, Pids) of - true -> T; - false -> not_found - end; - (_T, T) -> T - end, - not_found, - ModDbs) - end, - [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), - {reply, {ok, Bin}, State}; + case ets:lookup(Db, {Mod, refs}) of + [{{Mod, refs}, ModDbs}] -> + ModDb = + if + Pid =:= any -> hd(ModDbs); + true -> + lists:foldl(fun(T, not_found) -> + [{T, Pids}] = ets:lookup(Db, T), + case lists:member(Pid, Pids) of + true -> T; + false -> not_found + end; + (_T, T) -> T + end, + not_found, + ModDbs) + end, + [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), + {reply, {ok, Bin}, State}; + [] -> % code not interpreted + {reply, not_found, State} + end; handle_call({is_interpreted, Mod, Name, Arity}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl index 6af19af33b..29c8e8cefb 100644 --- a/lib/debugger/src/dbg_wx_trace.erl +++ b/lib/debugger/src/dbg_wx_trace.erl @@ -818,11 +818,14 @@ gui_show_module(Win, Mod, Line, _Cm, Pid, How) -> gui_load_module(Win, Mod, _Pid) -> dbg_wx_trace_win:display(Win,{text, "Loading module..."}), - %% Contents = int:contents(Mod, Pid), - {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), - Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), - dbg_wx_trace_win:display(Win,{text, ""}), - Win2. + case dbg_iserver:call({raw_contents, Mod, any}) of + {ok, Contents} -> + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2; + not_found -> + dbg_wx_trace_win:show_no_code(Win) + end. gui_update_bindings(Win,Meta) -> Bs = int:meta(Meta, bindings, nostack), diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl index 91fc0d08cb..86d009238f 100644 --- a/lib/debugger/src/dbg_wx_view.erl +++ b/lib/debugger/src/dbg_wx_view.erl @@ -263,7 +263,11 @@ shortcut(_) -> false. gui_load_module(Win, Mod) -> dbg_wx_trace_win:display(Win,{text, "Loading module..."}), - {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), - Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), - dbg_wx_trace_win:display(Win,{text, ""}), - Win2. + case dbg_iserver:call({raw_contents, Mod, any}) of + {ok, Contents} -> + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2; + not_found -> + dbg_wx_trace_win:show_no_code(Win) + end. diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index bcac8afe64..7f86520c06 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -282,7 +282,7 @@ cl_check_log(none) -> cl_check_log(Output) -> io:format(" Check output file `~s' for details\n", [Output]). --spec format_warning(raw_warning()) -> string(). +-spec format_warning(raw_warning() | dial_warning()) -> string(). format_warning(W) -> format_warning(W, basename). diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 976a2b8955..a72368f9f8 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -768,19 +768,9 @@ picky_contract_check(CSig0, Sig0, MFA, WarningInfo, Contract, RecDict, Acc) -> end. extra_contract_warning(MFA, WarningInfo, Contract, CSig, Sig, RecDict) -> - %% We do not want to depend upon erl_types:t_to_string() possibly - %% hiding the contents of opaque types. - SigUnopaque = erl_types:t_unopaque(Sig), - CSigUnopaque = erl_types:t_unopaque(CSig), - SigString0 = - lists:flatten(dialyzer_utils:format_sig(SigUnopaque, RecDict)), - ContractString0 = - lists:flatten(dialyzer_utils:format_sig(CSigUnopaque, RecDict)), - %% The only difference is in record fields containing 'undefined' or not. - IsUndefRecordFieldsRelated = SigString0 =:= ContractString0, {IsRemoteTypesRelated, SubtypeRelation} = is_remote_types_related(Contract, CSig, Sig, MFA, RecDict), - case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of + case IsRemoteTypesRelated of true -> no_warning; false -> diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 9399789464..963c953447 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -978,12 +978,21 @@ handle_case(Tree, Map, State) -> false -> State1 end, Map2 = join_maps_begin(Map1), - {MapList, State3, Type} = + {MapList, State3, Type, Warns} = handle_clauses(Clauses, Arg, ArgType, ArgType, State2, - [], Map2, [], []), + [], Map2, [], [], []), + %% Non-Erlang BEAM languages, such as Elixir, expand language constructs + %% into case statements. In that case, we do not want to warn on + %% individual clauses not matching unless none of them can. + SupressForced = is_compiler_generated(cerl:get_ann(Tree)) + andalso not (t_is_none(Type)), + State4 = lists:foldl(fun({T,R,M,F}, S) -> + state__add_warning( + S,T,R,M,F andalso (not SupressForced)) + end, State3, Warns), Map3 = join_maps_end(MapList, Map2), debug_pp_map(Map3), - {State3, Map3, Type} + {State4, Map3, Type} end. %%---------------------------------------- @@ -1082,22 +1091,24 @@ handle_receive(Tree, Map, State) -> RaceListSize + 1, State); false -> State end, - {MapList, State2, ReceiveType} = + {MapList, State2, ReceiveType, Warns} = handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map, - [], []), + [], [], []), + State3 = lists:foldl(fun({T,R,M,F}, S) -> state__add_warning(S,T,R,M,F) end, + State2, Warns), Map1 = join_maps(MapList, Map), - {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2), - Opaques = State3#state.opaques, + {State4, Map2, TimeoutType} = traverse(Timeout, Map1, State3), + Opaques = State4#state.opaques, case (t_is_atom(TimeoutType, Opaques) andalso (t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of true -> - {State3, Map2, ReceiveType}; + {State4, Map2, ReceiveType}; false -> Action = cerl:receive_action(Tree), - {State4, Map3, ActionType} = traverse(Action, Map, State3), + {State5, Map3, ActionType} = traverse(Action, Map, State4), Map4 = join_maps([Map3, Map1], Map), Type = t_sup(ReceiveType, ActionType), - {State4, Map4, Type} + {State5, Map4, Type} end. %%---------------------------------------- @@ -1245,7 +1256,7 @@ handle_tuple(Tree, Map, State) -> %% Clauses %% handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, - Acc, ClauseAcc) -> + Acc, ClauseAcc, WarnAcc0) -> IsRaceAnalysisEnabled = is_race_analysis_enabled(State), State1 = case IsRaceAnalysisEnabled of @@ -1258,8 +1269,8 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, State); false -> State end, - {State2, ClauseMap, BodyType, NewArgType} = - do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1), + {State2, ClauseMap, BodyType, NewArgType, WarnAcc} = + do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1, WarnAcc0), {NewClauseAcc, State3} = case IsRaceAnalysisEnabled of true -> @@ -1277,9 +1288,9 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, false -> {[BodyType|CaseTypes], [ClauseMap|Acc]} end, handle_clauses(Left, Arg, NewArgType, OrigArgType, State3, - NewCaseTypes, MapIn, NewAcc, NewClauseAcc); + NewCaseTypes, MapIn, NewAcc, NewClauseAcc, WarnAcc); handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, - ClauseAcc) -> + ClauseAcc, WarnAcc) -> State1 = case is_race_analysis_enabled(State) of true -> @@ -1289,9 +1300,9 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, RaceListSize + 1, State); false -> State end, - {lists:reverse(Acc), State1, t_sup(CaseTypes)}. + {lists:reverse(Acc), State1, t_sup(CaseTypes), WarnAcc}. -do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> +do_clause(C, Arg, ArgType0, OrigArgType, Map, State, Warns) -> Pats = cerl:clause_pats(C), Guard = cerl:clause_guard(C), Body = cerl:clause_body(C), @@ -1323,7 +1334,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> [cerl_prettypr:format(C), format_type(ArgType0, State1)]), case state__warning_mode(State1) of false -> - {State1, Map, t_none(), ArgType0}; + {State1, Map, t_none(), ArgType0, Warns}; true -> {Msg, Force} = case t_is_none(ArgType0) of @@ -1403,8 +1414,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {record_match, _} -> ?WARN_MATCHING; {pattern_match_cov, _} -> ?WARN_MATCHING end, - {state__add_warning(State1, WarnType, C, Msg, Force), - Map, t_none(), ArgType0} + {State1, Map, t_none(), ArgType0, [{WarnType, C, Msg, Force}|Warns]} end; {Map2, PatTypes} -> Map3 = @@ -1437,9 +1447,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> false -> {guard_fail_pat, [PatString, format_type(ArgType0, State1)]} end, - State2 = + Warn = case Reason of - none -> state__add_warning(State1, ?WARN_MATCHING, C, DefaultMsg); + none -> {?WARN_MATCHING, C, DefaultMsg, false}; {FailGuard, Msg} -> case is_compiler_generated(cerl:get_ann(FailGuard)) of false -> @@ -1448,15 +1458,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {neg_guard_fail, _} -> ?WARN_MATCHING; {opaque_guard, _} -> ?WARN_OPAQUE end, - state__add_warning(State1, WarnType, FailGuard, Msg); + {WarnType, FailGuard, Msg, false}; true -> - state__add_warning(State1, ?WARN_MATCHING, C, Msg) + {?WARN_MATCHING, C, Msg, false} end end, - {State2, Map, t_none(), NewArgType}; + {State1, Map, t_none(), NewArgType, [Warn|Warns]}; Map4 -> {RetState, RetMap, BodyType} = traverse(Body, Map4, State1), - {RetState, RetMap, BodyType, NewArgType} + {RetState, RetMap, BodyType, NewArgType, Warns} end end. diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile index f43e04dd59..0d8fba438c 100644 --- a/lib/dialyzer/test/Makefile +++ b/lib/dialyzer/test/Makefile @@ -12,6 +12,7 @@ AUXILIARY_FILES=\ dialyzer_common.erl\ file_utils.erl\ dialyzer_SUITE.erl\ + abstract_SUITE.erl\ plt_SUITE.erl # ---------------------------------------------------- diff --git a/lib/dialyzer/test/abstract_SUITE.erl b/lib/dialyzer/test/abstract_SUITE.erl new file mode 100644 index 0000000000..269db3e836 --- /dev/null +++ b/lib/dialyzer/test/abstract_SUITE.erl @@ -0,0 +1,102 @@ +%% This suite contains cases that cannot be written +%% in Erlang itself and must be done via the abstract +%% format. + +-module(abstract_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include("dialyzer_test_constants.hrl"). + +-export([suite/0, all/0, init_per_suite/0, init_per_suite/1]). +-export([generated_case/1]). + +suite() -> + [{timetrap, {minutes, 1}}]. +all() -> + [generated_case]. + +init_per_suite() -> + [{timetrap, ?plt_timeout}]. +init_per_suite(Config) -> + OutDir = ?config(priv_dir, Config), + case dialyzer_common:check_plt(OutDir) of + fail -> {skip, "Plt creation/check failed."}; + ok -> [{dialyzer_options, []}|Config] + end. + +generated_case(Config) when is_list(Config) -> + %% Equivalent to: + %% + %% -module(foo). + %% -export(bar). + %% bar() -> + %% Arg = sample, + %% case Arg of + %% #{} -> map; + %% _ -> Arg:fn() + %% end. + %% + %% Except the case statement and its clauses are marked as autogenerated. + [] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{atom,4,sample}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,[{location,6},{generated,true}],[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + %% With the first clause not auto-generated + [{warn_matching,{_,6},_}] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{atom,4,sample}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,6,[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + %% With Arg set to [] so neither clause matches + [{warn_return_no_exit,{_,3},_}, + {warn_matching,{_,6},_}, + {warn_failing_call,{_,7},_}] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{nil,4}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,[{location,6},{generated,true}],[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + ok. + +test(Prog, Config, COpts, DOpts) -> + {ok, BeamFile} = compile(Config, Prog, COpts), + run_dialyzer(Config, succ_typings, [BeamFile], DOpts). + +compile(Config, Prog, CompileOpts) -> + OutDir = ?config(priv_dir, Config), + Opts = [{outdir, OutDir}, debug_info, return_errors | CompileOpts], + {ok, Module, Source} = compile:forms(Prog, Opts), + BeamFile = filename:join([OutDir, lists:concat([Module, ".beam"])]), + ok = file:write_file(BeamFile, Source), + {ok, BeamFile}. + +run_dialyzer(Config, Analysis, Files, Opts) -> + OutDir = ?config(priv_dir, Config), + PltFilename = dialyzer_common:plt_file(OutDir), + dialyzer:run([{analysis_type, Analysis}, + {files, Files}, + {init_plt, PltFilename}, + {check_plt, false}, + {from, byte_code} | + Opts]). diff --git a/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options index 50991c9bc5..cb6a88786e 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options @@ -1 +1,2 @@ {dialyzer_options, []}. +{time_limit, 2}. diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl index d2b1026c06..48083a2731 100644 --- a/lib/dialyzer/test/dialyzer_common.erl +++ b/lib/dialyzer/test/dialyzer_common.erl @@ -7,7 +7,7 @@ -module(dialyzer_common). --export([check_plt/1, check/4, create_all_suites/0, new_tests/2]). +-export([check_plt/1, check/4, create_all_suites/0, new_tests/2, plt_file/1]). -include_lib("kernel/include/file.hrl"). @@ -39,7 +39,7 @@ check_plt(OutDir) -> io:format("Checking plt:"), - PltFilename = filename:join(OutDir, ?plt_filename), + PltFilename = plt_file(OutDir), case file:read_file_info(PltFilename) of {ok, _} -> dialyzer_check_plt(PltFilename); {error, _ } -> @@ -63,6 +63,11 @@ check_plt(OutDir) -> end end. +-spec plt_file(string()) -> string(). + +plt_file(OutDir) -> + filename:join(OutDir, ?plt_filename). + dialyzer_check_plt(PltFilename) -> try dialyzer:run([{analysis_type, plt_check}, {init_plt, PltFilename}]) of @@ -119,7 +124,7 @@ build_plt(PltFilename) -> 'same' | {differ, [term()]}. check(TestCase, Opts, Dir, OutDir) -> - PltFilename = filename:join(OutDir, ?plt_filename), + PltFilename = plt_file(OutDir), SrcDir = filename:join(Dir, ?input_files_directory), ResDir = filename:join(Dir, ?result_files_directory), Filename = filename:join(SrcDir, atom_to_list(TestCase)), diff --git a/lib/dialyzer/test/map_SUITE_data/src/opaque_bif.erl b/lib/dialyzer/test/map_SUITE_data/src/opaque_bif.erl new file mode 100644 index 0000000000..40214a1887 --- /dev/null +++ b/lib/dialyzer/test/map_SUITE_data/src/opaque_bif.erl @@ -0,0 +1,13 @@ +-module(opaque_bif). +-export([o1/1]). +-export_type([opaque_any_map/0]). +-opaque opaque_any_map() :: map(). + +%% ERL-249: A bug with opaque arguments to maps:merge/2 +%% Reported by Felipe Ripoll on 6/9/2016 +-spec o1(opaque_any_map()) -> opaque_any_map(). +o1(Map) -> + maps:merge(o1_c(), Map). + +-spec o1_c() -> opaque_any_map(). +o1_c() -> #{}. diff --git a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options index 3ff26b87db..ffdf8270c8 100644 --- a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options +++ b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options @@ -1 +1,2 @@ {dialyzer_options, [{warnings, [no_unused, no_return]}]}. +{time_limit, 2}. diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index d68a78ed6d..72181a42b0 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -94,12 +94,12 @@ in this module.</p> <taglist> -<tag><c>Address()</c></tag> -<tag><c>DiameterIdentity()</c></tag> -<tag><c>Grouped()</c></tag> -<tag><c>OctetString()</c></tag> -<tag><c>Time()</c></tag> -<tag><c>Unsigned32()</c></tag> +<tag><c>Address()</c></tag><item/> +<tag><c>DiameterIdentity()</c></tag><item/> +<tag><c>Grouped()</c></tag><item/> +<tag><c>OctetString()</c></tag><item/> +<tag><c>Time()</c></tag><item/> +<tag><c>Unsigned32()</c></tag><item/> <tag><c>UTF8String()</c></tag> <item> <p> @@ -159,8 +159,7 @@ Has one the following types.</p> <p> Unique identifier for the application in the scope of the service. -Defaults to the value of the <c>dictionary</c> option if -unspecified.</p> +Defaults to the value of the <c>dictionary</c> option.</p> </item> <tag><c>{dictionary, atom()}</c></tag> @@ -187,7 +186,7 @@ Initial callback state. The prevailing state is passed to some &man_app; callbacks, which can then return a new state. -Defaults to the value of the <c>alias</c> option if unspecified.</p> +Defaults to the value of the <c>alias</c> option.</p> </item> <tag><c>{call_mutates_state, true|false}</c></tag> @@ -195,7 +194,7 @@ Defaults to the value of the <c>alias</c> option if unspecified.</p> <p> Whether or not the &app_pick_peer; application callback can modify the application state. -Defaults to <c>false</c> if unspecified.</p> +Defaults to <c>false</c>.</p> <warning> <p> @@ -228,7 +227,7 @@ question is as if a callback had taken place and returned <c>{error, failure}</c>.</p> <p> -Defaults to <c>discard</c> if unspecified.</p> +Defaults to <c>discard</c>.</p> </item> <tag><c>{request_errors, answer_3xxx|answer|callback}</c></tag> @@ -249,7 +248,7 @@ place and its return value determines the answer sent to the peer, if any.</p> <p> -Defaults to <c>answer_3xxx</c> if unspecified.</p> +Defaults to <c>answer_3xxx</c>.</p> <note> <p> @@ -339,8 +338,8 @@ Has one of the following types.</p> <taglist> -<tag><c>{'Origin-Host', &dict_DiameterIdentity;}</c></tag> -<tag><c>{'Origin-Realm', &dict_DiameterIdentity;}</c></tag> +<tag><c>{'Origin-Host', &dict_DiameterIdentity;}</c></tag><item/> +<tag><c>{'Origin-Realm', &dict_DiameterIdentity;}</c></tag><item/> <tag><c>{'Host-IP-Address', [&dict_Address;]}</c></tag> <item> <p> @@ -352,8 +351,8 @@ question communicates an address list as described in &man_transport;</p> </item> -<tag><c>{'Vendor-Id', &dict_Unsigned32;}</c></tag> -<tag><c>{'Product-Name', &dict_UTF8String;}</c></tag> +<tag><c>{'Vendor-Id', &dict_Unsigned32;}</c></tag><item/> +<tag><c>{'Product-Name', &dict_UTF8String;}</c></tag><item/> <tag><c>{'Origin-State-Id', &dict_Unsigned32;}</c></tag> <item> <p> @@ -366,8 +365,8 @@ can be used as to retrieve a value that is computed when the diameter application is started.</p> </item> -<tag><c>{'Supported-Vendor-Id', [&dict_Unsigned32;]}</c></tag> -<tag><c>{'Auth-Application-Id', [&dict_Unsigned32;]}</c></tag> +<tag><c>{'Supported-Vendor-Id', [&dict_Unsigned32;]}</c></tag><item/> +<tag><c>{'Auth-Application-Id', [&dict_Unsigned32;]}</c></tag><item/> <tag><c>{'Inband-Security-Id', [&dict_Unsigned32;]}</c></tag> <item> <p> @@ -377,9 +376,9 @@ If 1 (TLS) is specified then TLS is selected if the CER/CEA received from the peer offers it.</p> </item> -<tag><c>{'Acct-Application-Id', [&dict_Unsigned32;]}</c></tag> -<tag><c>{'Vendor-Specific-Application-Id', [&dict_Grouped;]}</c></tag> -<tag><c>{'Firmware-Revision', &dict_Unsigned32;}</c></tag> +<tag><c>{'Acct-Application-Id', [&dict_Unsigned32;]}</c></tag><item/> +<tag><c>{'Vendor-Specific-Application-Id', [&dict_Grouped;]}</c></tag><item/> +<tag><c>{'Firmware-Revision', &dict_Unsigned32;}</c></tag><item/> </taglist> @@ -567,9 +566,8 @@ Can have one of the following types.</p> <taglist> -<tag><c>start</c></tag> +<tag><c>start</c></tag><item/> <tag><c>stop</c></tag> - <item> <p> The service is being started or stopped. @@ -578,8 +576,8 @@ No event follows a <c>stop</c> event, and this event implies the termination of all transport processes.</p> </item> -<tag><c>{up, Ref, Peer, Config, Pkt}</c></tag> -<tag><c>{up, Ref, Peer, Config}</c></tag> +<tag><c>{up, Ref, Peer, Config, Pkt}</c></tag><item/> +<tag><c>{up, Ref, Peer, Config}</c></tag><item/> <tag><c>{down, Ref, Peer, Config}</c></tag> <item> <pre> @@ -788,8 +786,8 @@ be matched by corresponding &capability; configuration, of </item> -<marker id="incoming_maxlen"/> -<tag><c>{incoming_maxlen, 0..16777215}</c></tag> +<tag> +<marker id="incoming_maxlen"/><c>{incoming_maxlen, 0..16777215}</c></tag> <item> <p> Bound on the expected size of incoming Diameter messages. @@ -917,8 +915,8 @@ Options <c>monitor</c> and <c>link</c> are ignored.</p> Defaults to the empty list.</p> </item> -<marker id="strict_mbit"/> -<tag><c>{strict_mbit, boolean()}</c></tag> +<tag> +<marker id="strict_mbit"/><c>{strict_mbit, boolean()}</c></tag> <item> <p> Whether or not to regard an AVP setting the M-bit as erroneous when @@ -935,7 +933,7 @@ Defaults to <c>true</c>.</p> <p> RFC 6733 is unclear about the semantics of the M-bit. One the one hand, the CCF specification in section 3.2 documents AVP -in a command grammar as meaning <b>any</b> arbitrary AVP; on the +in a command grammar as meaning <em>any</em> arbitrary AVP; on the other hand, 1.3.4 states that AVPs setting the M-bit cannot be added to an existing command: the modified command must instead be placed in a new Diameter application.</p> @@ -945,7 +943,7 @@ allowing arbitrary AVPs setting the M-bit in a command makes its interpretation implementation-dependent, since there's no guarantee that all implementations will understand the same set of arbitrary AVPs in the context of a given command. -However, interpreting <c>AVP</c> in a command grammar as <b>any</b> +However, interpreting <c>AVP</c> in a command grammar as any AVP, regardless of M-bit, renders 1.3.4 meaningless, since the receiver can simply ignore any AVP it thinks isn't relevant, regardless of the sender's intent.</p> @@ -960,8 +958,8 @@ occur in the message in question.</p> </item> -<marker id="string_decode"/> -<tag><c>{string_decode, boolean()}</c></tag> +<tag> +<marker id="string_decode"/><c>{string_decode, boolean()}</c></tag> <item> <p> Whether or not to decode AVPs of type &dict_OctetString; and its @@ -1031,8 +1029,9 @@ Option passed to &add_transport;. Has one of the following types.</p> <taglist> -<marker id="applications"/> -<tag><c>{applications, [&application_alias;]}</c></tag> + +<tag> +<marker id="applications"/><c>{applications, [&application_alias;]}</c></tag> <item> <p> Diameter applications to which the transport should be restricted. @@ -1050,8 +1049,8 @@ implies having to set matching *-Application-Id AVPs in a </item> -<marker id="capabilities"/> -<tag><c>{capabilities, [&capability;]}</c></tag> +<tag> +<marker id="capabilities"/><c>{capabilities, [&capability;]}</c></tag> <item> <p> AVPs used to construct outgoing CER/CEA messages. @@ -1064,8 +1063,8 @@ may be particularly appropriate for Inband-Security-Id, in case TLS is desired over TCP as implemented by &man_tcp;.</p> </item> -<marker id="capabilities_cb"/> -<tag><c>{capabilities_cb, &evaluable;}</c></tag> +<tag> +<marker id="capabilities_cb"/><c>{capabilities_cb, &evaluable;}</c></tag> <item> <p> Callback invoked upon reception of CER/CEA during capabilities @@ -1112,8 +1111,8 @@ case the corresponding callbacks are applied until either all return <c>ok</c> or one does not.</p> </item> -<marker id="capx_timeout"/> -<tag><c>{capx_timeout, &dict_Unsigned32;}</c></tag> +<tag> +<marker id="capx_timeout"/><c>{capx_timeout, &dict_Unsigned32;}</c></tag> <item> <p> Number of milliseconds after which a transport process having an @@ -1127,8 +1126,8 @@ For a listening transport, the peer determines the timing.</p> Defaults to 10000.</p> </item> -<marker id="connect_timer"/> -<tag><c>{connect_timer, Tc}</c></tag> +<tag> +<marker id="connect_timer"/><c>{connect_timer, Tc}</c></tag> <item> <pre> Tc = &dict_Unsigned32; @@ -1158,9 +1157,8 @@ Defaults to 30000 for a connecting transport and 60000 for a listening transport.</p> </item> -<marker id="disconnect_cb"/> -<tag><c>{disconnect_cb, &evaluable;}</c></tag> - +<tag> +<marker id="disconnect_cb"/><c>{disconnect_cb, &evaluable;}</c></tag> <item> <p> Callback invoked prior to terminating the transport process of a @@ -1236,8 +1234,8 @@ configured them.</p> Defaults to a single callback returning <c>dpr</c>.</p> </item> -<marker id="dpa_timeout"/> -<tag><c>{dpa_timeout, &dict_Unsigned32;}</c></tag> +<tag> +<marker id="dpa_timeout"/><c>{dpa_timeout, &dict_Unsigned32;}</c></tag> <item> <p> Number of milliseconds after which a transport connection is @@ -1247,8 +1245,8 @@ terminated following an outgoing DPR if DPA is not received.</p> Defaults to 1000.</p> </item> -<marker id="dpr_timeout"/> -<tag><c>{dpr_timeout, &dict_Unsigned32;}</c></tag> +<tag> +<marker id="dpr_timeout"/><c>{dpr_timeout, &dict_Unsigned32;}</c></tag> <item> <p> Number of milliseconds after which a transport connection is @@ -1259,8 +1257,8 @@ connection.</p> Defaults to 5000.</p> </item> -<marker id="length_errors"/> -<tag><c>{length_errors, exit|handle|discard}</c></tag> +<tag> +<marker id="length_errors"/><c>{length_errors, exit|handle|discard}</c></tag> <item> <p> How to deal with errors in the Message Length field of the @@ -1308,8 +1306,8 @@ the same peer.</p> </item> -<marker id="spawn_opt"/> -<tag><c>{spawn_opt, [term()]}</c></tag> +<tag> +<marker id="spawn_opt"/><c>{spawn_opt, [term()]}</c></tag> <item> <p> Options passed to &spawn_opt; when spawning a process for an @@ -1320,15 +1318,15 @@ Options <c>monitor</c> and <c>link</c> are ignored.</p> Defaults to the list configured on the service if not specified.</p> </item> -<marker id="transport_config"/> -<tag><c>{transport_config, term()}</c></tag> +<tag> +<marker id="transport_config"/><c>{transport_config, term()}</c></tag><item/> <tag><c>{transport_config, term(), &dict_Unsigned32; | infinity}</c></tag> <item> <p> Term passed as the third argument to the &transport_start; function of the relevant &transport_module; in order to start a transport process. -Defaults to the empty list if unspecified.</p> +Defaults to the empty list.</p> <p> The 3-tuple form additionally specifies an interval, in milliseconds, @@ -1349,12 +1347,12 @@ request a connection with one peer over SCTP or another To listen on both SCTP and TCP, define one transport for each.</p> </item> -<marker id="transport_module"/> -<tag><c>{transport_module, atom()}</c></tag> +<tag> +<marker id="transport_module"/><c>{transport_module, atom()}</c></tag> <item> <p> Module implementing a transport process as defined in &man_transport;. -Defaults to <c>diameter_tcp</c> if unspecified.</p> +Defaults to <c>diameter_tcp</c>.</p> <p> Multiple <c>transport_module</c> and &transport_config; @@ -1369,8 +1367,9 @@ modules in order until one establishes a connection within the corresponding timeout (see below) or all fail.</p> </item> -<marker id="watchdog_config"/> -<tag><c>{watchdog_config, [{okay|suspect, non_neg_integer()}]}</c></tag> +<tag> +<marker id="watchdog_config"/><c>{watchdog_config, + [{okay|suspect, non_neg_integer()}]}</c></tag> <item> <p> Configuration that alters the behaviour of the watchdog @@ -1393,8 +1392,8 @@ misbehaving nodes during test.</p> </warning> </item> -<marker id="watchdog_timer"/> -<tag><c>{watchdog_timer, TwInit}</c></tag> +<tag> +<marker id="watchdog_timer"/><c>{watchdog_timer, TwInit}</c></tag> <item> <pre> TwInit = &dict_Unsigned32; @@ -1412,7 +1411,7 @@ the callback.</p> <p> An integer value must be at least 6000 as required by RFC 3539. -Defaults to 30000 if unspecified.</p> +Defaults to 30000.</p> </item> </taglist> @@ -1422,10 +1421,10 @@ Unrecognized options are silently ignored but are returned unmodified by &service_info; and can be referred to in predicate functions passed to &remove_transport;.</p> -<marker id="transport_ref"/> </item> -<tag><c>transport_ref() = reference()</c></tag> +<tag> +<marker id="transport_ref"/><c>transport_ref() = reference()</c></tag> <item> <p> Reference returned by &add_transport; that @@ -1682,17 +1681,17 @@ returned.</p> <taglist> -<tag><c>'Origin-Host'</c></tag> -<tag><c>'Origin-Realm'</c></tag> -<tag><c>'Vendor-Id'</c></tag> -<tag><c>'Product-Name'</c></tag> -<tag><c>'Origin-State-Id'</c></tag> -<tag><c>'Host-IP-Address'</c></tag> -<tag><c>'Supported-Vendor'</c></tag> -<tag><c>'Auth-Application-Id'</c></tag> -<tag><c>'Inband-Security-Id'</c></tag> -<tag><c>'Acct-Application-Id'</c></tag> -<tag><c>'Vendor-Specific-Application-Id'</c></tag> +<tag><c>'Origin-Host'</c></tag><item/> +<tag><c>'Origin-Realm'</c></tag><item/> +<tag><c>'Vendor-Id'</c></tag><item/> +<tag><c>'Product-Name'</c></tag><item/> +<tag><c>'Origin-State-Id'</c></tag><item/> +<tag><c>'Host-IP-Address'</c></tag><item/> +<tag><c>'Supported-Vendor'</c></tag><item/> +<tag><c>'Auth-Application-Id'</c></tag><item/> +<tag><c>'Inband-Security-Id'</c></tag><item/> +<tag><c>'Acct-Application-Id'</c></tag><item/> +<tag><c>'Vendor-Specific-Application-Id'</c></tag><item/> <tag><c>'Firmware-Revision'</c></tag> <item> <p> diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml index 973b6eb732..dfcd00975b 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -90,17 +90,14 @@ is called in response to an incoming Diameter request message.</p> </list> -</description> - -<note> <p> -The arities given for the the callback functions here assume no extra -arguments. +The arities for the the callback functions here assume no extra arguments. All functions will also be passed any extra arguments configured with the callback module itself when calling &mod_start_service; and, for the call-specific callbacks, any extra arguments passed to &mod_call;.</p> -</note> + +</description> <!-- ===================================================================== --> <!-- ===================================================================== --> @@ -110,9 +107,8 @@ and, for the call-specific callbacks, any extra arguments passed to <taglist> -<marker id="capabilities"/> - -<tag><c>capabilities() = #diameter_caps{}</c></tag> +<tag> +<marker id="capabilities"/><c>capabilities() = #diameter_caps{}</c></tag> <item> <p> A record containing the identities of @@ -126,9 +122,8 @@ Optional or possibly multiple values are encoded as lists of values, mandatory values as the bare value.</p> </item> -<marker id="message"/> - -<tag><c>message() = &codec_message;</c></tag> +<tag> +<marker id="message"/><c>message() = &codec_message;</c></tag> <item> <p> The representation of a Diameter message as passed to @@ -136,9 +131,8 @@ The representation of a Diameter message as passed to </item> -<marker id="packet"/> - -<tag><c>packet() = &codec_packet;</c></tag> +<tag> +<marker id="packet"/><c>packet() = &codec_packet;</c></tag> <item> <p> A container for incoming and outgoing Diameter messages that's passed @@ -146,25 +140,22 @@ through encode/decode and transport. Fields should not be set in return values except as documented.</p> </item> -<marker id="peer_ref"/> - -<tag><c>peer_ref() = term()</c></tag> +<tag> +<marker id="peer_ref"/><c>peer_ref() = term()</c></tag> <item> <p> A term identifying a transport connection with a Diameter peer.</p> </item> -<marker id="peer"/> - -<tag><c>peer() = {&peer_ref;, &capabilities;}</c></tag> +<tag> +<marker id="peer"/><c>peer() = {&peer_ref;, &capabilities;}</c></tag> <item> <p> A tuple representing a Diameter peer connection.</p> </item> -<marker id="state"/> - -<tag><c>state() = term()</c></tag> +<tag> +<marker id="state"/><c>state() = term()</c></tag> <item> <p> The state maintained by the application callback functions diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml index a0313e2877..91e96058dd 100644 --- a/lib/diameter/doc/src/diameter_codec.xml +++ b/lib/diameter/doc/src/diameter_codec.xml @@ -88,10 +88,9 @@ files resulting from dictionary file compilation.</p> <taglist> -<marker id="integers"/> - -<tag><c>uint8() = 0..255</c></tag> -<tag><c>uint24() = 0..16777215</c></tag> +<tag> +<marker id="integers"/><c>uint8() = 0..255</c></tag><item/> +<tag><c>uint24() = 0..16777215</c></tag><item/> <tag><c>uint32() = 0..4294967295</c></tag> <item> <p> @@ -99,9 +98,8 @@ files resulting from dictionary file compilation.</p> headers.</p> </item> -<marker id="avp"/> - -<tag><c>avp() = #diameter_avp{}</c></tag> +<tag> +<marker id="avp"/><c>avp() = #diameter_avp{}</c></tag> <item> <p> The application-neutral representation of an AVP. @@ -116,9 +114,9 @@ Fields have the following types.</p> <taglist> -<tag><c>code = uint32()</c></tag> -<tag><c>is_mandatory = boolean()</c></tag> -<tag><c>need_encryption = boolean()</c></tag> +<tag><c>code = uint32()</c></tag><item/> +<tag><c>is_mandatory = boolean()</c></tag><item/> +<tag><c>need_encryption = boolean()</c></tag><item/> <tag><c>vendor_id = uint32() | undefined</c></tag> <item> <p> @@ -167,9 +165,8 @@ Possible types are <c>undefined</c> and the Diameter types: </item> -<marker id="dictionary"/> - -<tag><c>dictionary() = module()</c></tag> +<tag> +<marker id="dictionary"/><c>dictionary() = module()</c></tag> <item> <p> @@ -179,9 +176,8 @@ The interface provided by a dictionary module is an implementation detail that may change.</p> </item> -<marker id="header"/> - -<tag><c>header() = #diameter_header{}</c></tag> +<tag> +<marker id="header"/><c>header() = #diameter_header{}</c></tag> <item> <p> The record representation of the Diameter header. @@ -204,11 +200,11 @@ Fields have the following types.</p> <taglist> -<tag><c>version = uint8()</c></tag> -<tag><c>length = uint24()</c></tag> -<tag><c>cmd_code = uint24()</c></tag> -<tag><c>application_id = uint32()</c></tag> -<tag><c>hop_by_hop_id = uint32()</c></tag> +<tag><c>version = uint8()</c></tag><item/> +<tag><c>length = uint24()</c></tag><item/> +<tag><c>cmd_code = uint24()</c></tag><item/> +<tag><c>application_id = uint32()</c></tag><item/> +<tag><c>hop_by_hop_id = uint32()</c></tag><item/> <tag><c>end_to_end_id = uint32()</c></tag> <item> <p> @@ -217,9 +213,9 @@ Hop-by-Hop Identifier and End-to-End Identifier fields of the Diameter header.</p> </item> -<tag><c>is_request = boolean()</c></tag> -<tag><c>is_proxiable = boolean()</c></tag> -<tag><c>is_error = boolean()</c></tag> +<tag><c>is_request = boolean()</c></tag><item/> +<tag><c>is_proxiable = boolean()</c></tag><item/> +<tag><c>is_error = boolean()</c></tag><item/> <tag><c>is_retransmitted = boolean()</c></tag> <item> <p> @@ -232,9 +228,8 @@ header.</p> </item> -<marker id="message"/> - -<tag><c>message() = record() | list()</c></tag> +<tag> +<marker id="message"/><c>message() = record() | list()</c></tag> <item> <p> The representation of a Diameter message as passed to @@ -257,9 +252,8 @@ question: messages are sent exactly as specified.</p> </item> -<marker id="packet"/> - -<tag><c>packet() = #diameter_packet{}</c></tag> +<tag> +<marker id="packet"/><c>packet() = #diameter_packet{}</c></tag> <item> <p> A container for incoming and outgoing Diameter messages. @@ -296,7 +290,7 @@ corresponding values.</p> <warning> <p> -A record-valued <c>msg</c> field does <b>not</b> imply an absence of +A record-valued <c>msg</c> field does <em>not</em> imply an absence of decode errors. The <c>errors</c> field should also be examined.</p> </warning> diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index ae40f99aee..9584d682c2 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" ?> -<!DOCTYPE erlref SYSTEM "fileref.dtd" [ +<!DOCTYPE fileref SYSTEM "fileref.dtd" [ <!ENTITY format '<seealso marker="#FILE_FORMAT">FILE FORMAT</seealso>'> <!ENTITY records @@ -121,9 +121,8 @@ The order in which sections are specified is unimportant.</p> <taglist> -<marker id="id"/> - -<tag><c>@id Number</c></tag> +<tag> +<marker id="id"/><c>@id Number</c></tag> <item> <p> Defines the integer Number as the Diameter Application Id of the @@ -146,14 +145,13 @@ Example:</p> </item> -<marker id="name"/> - -<tag><c>@name Mod</c></tag> +<tag> +<marker id="name"/><c>@name Mod</c></tag> <item> <p> Defines the name of the generated dictionary module. Can occur at most once and defaults to the name of the dictionary file -minus any extension if unspecified. +minus any extension. The section has empty content.</p> <p> @@ -169,9 +167,8 @@ Example:</p> </item> -<marker id="prefix"/> - -<tag><c>@prefix Name</c></tag> +<tag> +<marker id="prefix"/><c>@prefix Name</c></tag> <item> <p> Defines Name as the prefix to be added to record and constant names @@ -194,9 +191,8 @@ Example:</p> </item> -<marker id="vendor"/> - -<tag><c>@vendor Number Name</c></tag> +<tag> +<marker id="vendor"/><c>@vendor Number Name</c></tag> <item> <p> Defines the integer Number as the the default Vendor-Id of AVPs for @@ -216,9 +212,8 @@ Example:</p> </item> -<marker id="avp_vendor_id"/> - -<tag><c>@avp_vendor_id Number</c></tag> +<tag> +<marker id="avp_vendor_id"/><c>@avp_vendor_id Number</c></tag> <item> <p> Defines the integer Number as the Vendor-Id of the AVPs listed in the @@ -238,9 +233,8 @@ Region-Set </item> -<marker id="inherits"/> - -<tag><c>@inherits Mod</c></tag> +<tag> +<marker id="inherits"/><c>@inherits Mod</c></tag> <item> <p> Defines the name of a dictionary module containing AVP @@ -274,9 +268,8 @@ Example:</p> </pre> </item> -<marker id="avp_types"/> - -<tag><c>@avp_types</c></tag> +<tag> +<marker id="avp_types"/><c>@avp_types</c></tag> <item> <p> Defines the name, code, type and flags of individual AVPs. @@ -308,9 +301,8 @@ The P flag has been deprecated by &the_rfc;.</p> </item> -<marker id="custom_types"/> - -<tag><c>@custom_types Mod</c></tag> +<tag> +<marker id="custom_types"/><c>@custom_types Mod</c></tag> <item> <p> Specifies AVPs for which module Mod provides encode/decode functions. @@ -331,9 +323,8 @@ Framed-IP-Address </pre> </item> -<marker id="codecs"/> - -<tag><c>@codecs Mod</c></tag> +<tag> +<marker id="codecs"/><c>@codecs Mod</c></tag> <item> <p> Like <c>@custom_types</c> but requires the specified module to export @@ -350,9 +341,8 @@ Framed-IP-Address </pre> </item> -<marker id="messages"/> - -<tag><c>@messages</c></tag> +<tag> +<marker id="messages"/><c>@messages</c></tag> <item> <p> Defines the messages of the application. @@ -397,9 +387,8 @@ RTA ::= < Diameter Header: 287, PXY > </item> -<marker id="grouped"/> - -<tag><c>@grouped</c></tag> +<tag> +<marker id="grouped"/><c>@grouped</c></tag> <item> <p> Defines the contents of the AVPs of the application having type @@ -424,9 +413,8 @@ Specifying a Vendor-Id in the definition of a grouped AVP is equivalent to specifying it with <c>@avp_vendor_id</c>.</p> </item> -<marker id="enum"/> - -<tag><c>@enum Name</c></tag> +<tag> +<marker id="enum"/><c>@enum Name</c></tag> <item> <p> Defines values of AVP Name having type Enumerated. @@ -452,9 +440,8 @@ REMOVE_SIP_SERVER 3 </pre> </item> -<marker id="end"/> - -<tag><c>@end</c></tag> +<tag> +<marker id="end"/><c>@end</c></tag> <item> <p> Causes parsing of the dictionary to terminate: diff --git a/lib/diameter/doc/src/diameter_examples.xml b/lib/diameter/doc/src/diameter_examples.xml index 853ef96bb3..2e1f2b3c03 100644 --- a/lib/diameter/doc/src/diameter_examples.xml +++ b/lib/diameter/doc/src/diameter_examples.xml @@ -42,4 +42,3 @@ Example code can be found in the diameter application's <c>examples</c> subdirectory.</p> </chapter> - diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml index 00ccc39c15..6ca280c52b 100644 --- a/lib/diameter/doc/src/diameter_tcp.xml +++ b/lib/diameter/doc/src/diameter_tcp.xml @@ -150,7 +150,7 @@ Options <c>binary</c>, <c>packet</c> and <c>active</c> cannot be specified. Also, option <c>port</c> can be specified for a listening transport to specify the local listening port, the default being the standardized -3868 if unspecified. +3868. Note that the option <c>ip</c> specifies the local address.</p> <p> diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml index 736d4cbfbd..294e8a8864 100644 --- a/lib/diameter/doc/src/diameter_transport.xml +++ b/lib/diameter/doc/src/diameter_transport.xml @@ -65,9 +65,8 @@ parent).</p> <taglist> -<marker id="message"/> - -<tag><c>message() = binary() | &codec_packet;</c></tag> +<tag> +<marker id="message"/><c>message() = binary() | &codec_packet;</c></tag> <item> <p> A Diameter message as passed over the transport interface.</p> @@ -160,9 +159,9 @@ It should exit if its transport connection with its peer is lost.</p> </funcs> <!-- ===================================================================== --> -<marker id="MESSAGES"/> <section> +<marker id="MESSAGES"/> <title>MESSAGES</title> <p> @@ -234,7 +233,7 @@ established a connection with the peer. Not sent if the transport process has <c>Type=connect</c>.</p> </item> -<tag><c>{diameter, {self(), connected, Remote}}</c></tag> +<tag><c>{diameter, {self(), connected, Remote}}</c></tag><item/> <tag><c>{diameter, {self(), connected, Remote, [LocalAddr]}}</c></tag> <item> <p> diff --git a/lib/diameter/doc/src/diameterc.xml b/lib/diameter/doc/src/diameterc.xml index 5bffe9a771..8f1c660989 100644 --- a/lib/diameter/doc/src/diameterc.xml +++ b/lib/diameter/doc/src/diameterc.xml @@ -11,7 +11,7 @@ <comref> <header> <copyright> -<year>2011</year><year>2013</year> +<year>2011</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -79,14 +79,14 @@ Write generated source to the specified directory. Defaults to the current working directory.</p> </item> -<tag><![CDATA[-E]]></tag> +<tag><![CDATA[-E]]></tag><item/> <tag><![CDATA[-H]]></tag> <item> <p> Suppress erl and hrl generation, respectively.</p> </item> -<tag><![CDATA[--name <name>]]></tag> +<tag><![CDATA[--name <name>]]></tag><item/> <tag><![CDATA[--prefix <prefix>]]></tag> <item> <p> diff --git a/lib/diameter/examples/code/relay.erl b/lib/diameter/examples/code/relay.erl index 3846b1d161..cf4ce8848b 100644 --- a/lib/diameter/examples/code/relay.erl +++ b/lib/diameter/examples/code/relay.erl @@ -53,7 +53,7 @@ {'Auth-Application-Id', [16#FFFFFFFF]}, {string_decode, false}, {application, [{alias, relay}, - {dictionary, diameter_relay}, + {dictionary, diameter_gen_relay}, {module, relay_cb}]}]). %% start/1 diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index 7f61620fc1..6bf748a727 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -123,7 +123,7 @@ ERL_COMPILE_FLAGS += \ # erl/hrl from dictionary file. gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia $(dia_verbose) \ - ../bin/diameterc -o gen -i $(EBIN) $< + escript ../bin/diameterc -o gen -i $(EBIN) $< opt: $(TARGET_FILES) diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl index b835e87967..3928769b5e 100644 --- a/lib/diameter/src/base/diameter_lib.erl +++ b/lib/diameter/src/base/diameter_lib.erl @@ -299,8 +299,28 @@ spawn_opts(server, Opts) -> spawn_opts(worker, Opts) -> opts(5000, Opts). -opts(HeapSize, Opts) -> - [{min_heap_size, HeapSize} | lists:keydelete(min_heap_size, 1, Opts)]. +%% These setting are historical rather than useful. In particular, the +%% server setting can bloat many processes unnecessarily. Let them be +%% disabled with -diameter min_heap_size false. + +opts(Def, Opts) -> + Key = min_heap_size, + case getenv(Key, Def) of + N when is_integer(N), 0 =< N -> + [{Key, N} | lists:keydelete(Key, 1, Opts)]; + _ -> + Opts + end. + +%% getenv/1 + +getenv(Key, Def) -> + case application:get_env(Key) of + {ok, T} -> + T; + undefined -> + Def + end. %% --------------------------------------------------------------------------- %% # wait/1 diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 2112941d5e..d93a3e71e3 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -123,8 +123,16 @@ peer_up(TPid) -> %% --------------------------------------------------------------------------- peer_down(TPid) -> - ets:delete(?REQUEST_TABLE, TPid), - failover(TPid). + ets:delete_object(?REQUEST_TABLE, {TPid}), + lists:foreach(fun failover/1, ets:lookup(?REQUEST_TABLE, TPid)). +%% Note that a request process can store its request after failover +%% notifications are sent here: insert_request/2 sends the notification +%% in that case. + +%% failover/1 + +failover({_TPid, {Pid, TRef}}) -> + Pid ! {failover, TRef}. %% --------------------------------------------------------------------------- %% incr/4 @@ -911,7 +919,7 @@ failed(Rec, FailedAvp, Dict) -> {'Failed-AVP', [FailedAvp]} catch error: _ -> - Avps = Dict:'get-'('AVP', Rec), + Avps = Dict:'#get-'('AVP', Rec), A = #diameter_avp{name = 'Failed-AVP', value = FailedAvp}, {'AVP', [A|Avps]} @@ -1452,7 +1460,7 @@ make_request_packet(#diameter_packet{header = Hdr} = Pkt, make_request_packet(Msg, Pkt) -> Pkt#diameter_packet{msg = Msg}. -%% make_retransmit_packet/2 +%% make_retransmit_packet/1 make_retransmit_packet(#diameter_packet{msg = [#diameter_header{} = Hdr | Avps]} @@ -1703,16 +1711,13 @@ send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, _SvcName, Timeout) when node() == node(TPid) -> Seqs = diameter_codec:sequence_numbers(Bin), TRef = erlang:start_timer(Timeout, self(), TPid), - Entry = {Seqs, Req, TRef}, + Entry = {Seqs, #request{handler = Pid} = Req, TRef}, - %% Ensure that request table is cleaned even if we receive an exit - %% signal. An alternative would be to simply trap exits, but - %% callbacks are applied in this process, and these could possibly - %% be expecting the prevailing behaviour. - Self = self(), - spawn(fun() -> diameter_lib:wait([Self]), erase_request(Entry) end), + %% Ensure that request table is cleaned even if the process is + %% killed. + spawn(fun() -> diameter_lib:wait([Pid]), delete_request(Entry) end), - store_request(Entry, TPid), + insert_request(Entry), send(TPid, Pkt), TRef; @@ -1774,6 +1779,8 @@ retransmit({TPid, Caps, App} SvcName, Timeout, []). +%% When sending a binary, it's up to prepare_retransmit to modify it +%% accordingly. retransmit({send, Msg}, Transport, @@ -1818,15 +1825,21 @@ resend_request(Pkt0, TRef = send_request(TPid, Pkt, Req, SvcName, Tmo), {TRef, Req}. -%% store_request/2 +%% insert_request/1 -store_request(T, TPid) -> - ets:insert(?REQUEST_TABLE, T), - ets:member(?REQUEST_TABLE, TPid) - orelse begin - {_Seqs, _Req, TRef} = T, - self() ! {failover, TRef} %% failover/1 may have missed - end. +insert_request({_Seqs, #request{transport = TPid}, TRef} = T) -> + ets:insert(?REQUEST_TABLE, [T, {TPid, {self(), TRef}}]), + is_peer_up(TPid) + orelse (self() ! {failover, TRef}). %% failover/1 may have missed + +%% is_peer_up/1 +%% +%% Is the entry written by peer_up/1 and deleted by peer_down/1 still +%% in the request table? + +is_peer_up(TPid) -> + Spec = [{{TPid}, [], ['$_']}], + '$end_of_table' /= ets:select(?REQUEST_TABLE, Spec, 1). %% lookup_request/2 %% @@ -1846,16 +1859,11 @@ lookup_request(Msg, TPid) -> false end. -%% erase_request/1 - -erase_request(T) -> - ets:delete_object(?REQUEST_TABLE, T). - -%% match_requests/1 +%% delete_request/1 -match_requests(TPid) -> - Pat = {'_', #request{transport = TPid, _ = '_'}, '_'}, - ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}]). +delete_request({_Seqs, #request{handler = Pid, transport = TPid}, TRef} = T) -> + Spec = [{R, [], [true]} || R <- [T, {TPid, {Pid, TRef}}]], + ets:select_delete(?REQUEST_TABLE, Spec). %% have_request/2 @@ -1864,28 +1872,6 @@ have_request(Pkt, TPid) -> Pat = {Seqs, #request{transport = TPid, _ = '_'}, '_'}, '$end_of_table' /= ets:select(?REQUEST_TABLE, [{Pat, [], ['$_']}], 1). -%% --------------------------------------------------------------------------- -%% # failover/1-2 -%% --------------------------------------------------------------------------- - -failover(TPid) - when is_pid(TPid) -> - lists:foreach(fun failover/1, match_requests(TPid)); -%% Note that a request process can store its request after failover -%% notifications are sent here: store_request/2 sends the notification -%% in that case. - -%% Failover as a consequence of peer_down/1: inform the -%% request process. -failover({_, Req, TRef}) -> - #request{handler = Pid, - packet = #diameter_packet{msg = M}} - = Req, - M /= undefined andalso (Pid ! {failover, TRef}). -%% Failover is not performed when msg = binary() since sending -%% pre-encoded binaries is only partially supported. (Mostly for -%% test.) - %% get_destination/2 get_destination(Dict, Msg) -> diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index cdaa9aa7f9..4007d6b7b1 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -790,20 +790,7 @@ header() -> ("%% -------------------------------------------------------------------\n" "%% This is a generated file.\n" "%% -------------------------------------------------------------------\n" - "\n" - "%%\n" - "%% Copyright (c) Ericsson AB. All rights reserved.\n" - "%%\n" - "%% The information in this document is the property of Ericsson.\n" - "%%\n" - "%% Except as specifically authorized in writing by Ericsson, the\n" - "%% receiver of this document shall keep the information contained\n" - "%% herein confidential and shall protect the same in whole or in\n" - "%% part from disclosure and dissemination to third parties.\n" - "%%\n" - "%% Disclosure and disseminations to the receivers employees shall\n" - "%% only be made on a strict need to know basis.\n" - "%%\n\n"). + "\n"). hrl_header(Name) -> header() ++ "-hrl_name('" ++ ?S(Name) ++ ".hrl').\n". diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index 618d5a7f10..b1b8e38d39 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -49,7 +49,11 @@ {"1.10", [{restart_application, diameter}]}, %% 18.0 {"1.11", [{restart_application, diameter}]}, %% 18.1 {"1.11.1", [{restart_application, diameter}]}, %% 18.2 - {"1.11.2", [{restart_application, diameter}]} %% 18.3 + {"1.11.2", [{restart_application, diameter}]}, %% 18.3 + {"1.12", [{load_module, diameter_lib}, %% 19.0 + {load_module, diameter_traffic}, + {load_module, diameter_tcp}, + {load_module, diameter_sctp}]} ], [ {"0.9", [{restart_application, diameter}]}, @@ -80,6 +84,10 @@ {"1.10", [{restart_application, diameter}]}, {"1.11", [{restart_application, diameter}]}, {"1.11.1", [{restart_application, diameter}]}, - {"1.11.2", [{restart_application, diameter}]} + {"1.11.2", [{restart_application, diameter}]}, + {"1.12", [{load_module, diameter_sctp}, + {load_module, diameter_tcp}, + {load_module, diameter_traffic}, + {load_module, diameter_lib}]} ] }. diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 4a005b853d..f48e4347ee 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -98,7 +98,7 @@ -record(listener, {ref :: reference(), socket :: gen_sctp:sctp_socket(), - count = 0 :: uint(), %% attached transport processes + service = false :: false | pid(), %% service process pending = {0, queue:new()}, accept :: [match()]}). %% Field pending implements two queues: the first of transport-to-be @@ -129,11 +129,14 @@ -> {ok, pid(), [inet:ip_address()]} when Ref :: diameter:transport_ref(). -start(T, #diameter_service{capabilities = Caps}, Opts) +start(T, Svc, Opts) when is_list(Opts) -> + #diameter_service{capabilities = Caps, + pid = SPid} + = Svc, diameter_sctp_sup:start(), %% start supervisors on demand Addrs = Caps#diameter_caps.host_ip_address, - s(T, Addrs, lists:map(fun ip/1, Opts)). + s(T, Addrs, SPid, lists:map(fun ip/1, Opts)). ip({ifaddr, A}) -> {ip, A}; @@ -144,18 +147,22 @@ ip(T) -> %% when there is not yet an association to assign it, or at comm_up on %% a new association in which case the call retrieves a transport from %% the pending queue. -s({accept, Ref} = A, Addrs, Opts) -> - {LPid, LAs} = listener(Ref, {Opts, Addrs}), - try gen_server:call(LPid, {A, self()}, infinity) of - {ok, TPid} -> {ok, TPid, LAs} +s({accept, Ref} = A, Addrs, SPid, Opts) -> + {ok, LPid, LAs} = listener(Ref, {Opts, Addrs}), + try gen_server:call(LPid, {A, self(), SPid}, infinity) of + {ok, TPid} -> + {ok, TPid, LAs}; + No -> + {error, No} catch - exit: Reason -> {error, Reason} + exit: Reason -> + {error, Reason} end; %% This implementation is due to there being no accept call in %% gen_sctp in order to be able to accept a new association only %% *after* an accepting transport has been spawned. -s({connect = C, Ref}, Addrs, Opts) -> +s({connect = C, Ref}, Addrs, _SPid, Opts) -> diameter_sctp_sup:start_child({C, self(), Opts, Addrs, Ref}). %% start_link/1 @@ -281,24 +288,23 @@ i({K, Ref}, #transport{mode = {accept, _}} = S) -> %% Accepting processes can be started concurrently: ensure only one %% listener is started. -listener(LRef, T) -> - diameter_sync:call({?MODULE, listener, LRef}, - {?MODULE, listener, [{LRef, T}]}, +listener(Ref, T) -> + diameter_sync:call({?MODULE, listener, Ref}, + {?MODULE, listener, [{Ref, T}]}, infinity, infinity). -listener({LRef, T}) -> - l(diameter_reg:match({?MODULE, listener, {LRef, '_'}}), LRef, T). +listener({Ref, T}) -> + l(diameter_reg:match({?MODULE, listener, {Ref, '_'}}), Ref, T). %% Existing listening process ... l([{{?MODULE, listener, {_, AS}}, LPid}], _, _) -> - {LAs, _Sock} = AS, - {LPid, LAs}; + {LAs, _Sock} = AS, + {ok, LPid, LAs}; %% ... or not. -l([], LRef, T) -> - {ok, LPid, LAs} = diameter_sctp_sup:start_child({listen, LRef, T}), - {LPid, LAs}. +l([], Ref, T) -> + diameter_sctp_sup:start_child({listen, Ref, T}). %% open/3 @@ -364,11 +370,17 @@ type(T) -> %% # handle_call/3 %% --------------------------------------------------------------------------- -handle_call({{accept, Ref}, Pid}, _, #listener{ref = Ref, - count = K} - = S) -> +handle_call({{accept, Ref}, Pid}, _, #listener{ref = Ref} = S) -> {TPid, NewS} = accept(Ref, Pid, S), - {reply, {ok, TPid}, NewS#listener{count = K+1}}; + {reply, {ok, TPid}, NewS}; + +handle_call({{accept, _} = T, Pid, SPid}, From, #listener{service = P} = S) -> + handle_call({T, Pid}, From, if not is_pid(P), is_pid(SPid) -> + monitor(process, SPid), + S#listener{service = SPid}; + true -> + S + end); handle_call(_, _, State) -> {reply, nok, State}. @@ -441,6 +453,13 @@ l({sctp, Sock, _RA, _RP, Data} = T, #listener{socket = Sock, setopts(Sock), NewS; +%% Service process has died. +l({'DOWN', _, process, Pid, _} = T, #listener{service = Pid, + socket = Sock}) -> + gen_sctp:close(Sock), + x(T); + +%% Accepting process has died. l({'DOWN', _MRef, process, TPid, _}, #listener{pending = {_,Q}} = S) -> down(queue:member(TPid, Q), TPid, S); @@ -454,20 +473,17 @@ l({transport, remove, _} = T, #listener{socket = Sock}) -> %% Accepting transport has died. %% One that's waiting for transport start in the pending queue ... -down(true, TPid, #listener{pending = {N,Q}, - count = K} - = S) -> +down(true, TPid, #listener{pending = {N,Q}} = S) -> NQ = queue:filter(fun(P) -> P /= TPid end, Q), if N < 0 -> %% awaiting an association ... - S#listener{count = K-1, - pending = {N+1, NQ}}; + S#listener{pending = {N+1, NQ}}; true -> %% ... or one has been assigned S#listener{pending = {N-1, NQ}} end; %% ... or one that's already attached. -down(false, _TPid, #listener{count = K} = S) -> - S#listener{count = K-1}. +down(false, _TPid, S) -> + S. %% t/2 %% diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl index 546c2cfa5e..44abc5c3b4 100644 --- a/lib/diameter/src/transport/diameter_tcp.erl +++ b/lib/diameter/src/transport/diameter_tcp.erl @@ -71,11 +71,8 @@ %% a process owning the listening port. %% Listener process state. --record(listener, {socket :: inet:socket(), - count = 1 :: non_neg_integer()}). %% accepting processes -%% The count of accepting processes was previously used to terminate -%% the listening process, but diameter_reg:subscribe/2 is now used for -%% this. Leave the the count for trace purposes. +-record(listener, {socket :: inet:socket(), + service = false :: false | pid()}). %% service process %% Monitor process state. -record(monitor, @@ -138,11 +135,15 @@ | {ok, pid()} when Ref :: diameter:transport_ref(). -start({T, Ref}, #diameter_service{capabilities = Caps}, Opts) -> +start({T, Ref}, Svc, Opts) -> + #diameter_service{capabilities = Caps, + pid = SPid} + = Svc, + diameter_tcp_sup:start(), %% start tcp supervisors on demand {Mod, Rest} = split(Opts), Addrs = Caps#diameter_caps.host_ip_address, - Arg = {T, Ref, Mod, self(), Rest, Addrs}, + Arg = {T, Ref, Mod, self(), Rest, Addrs, SPid}, diameter_tcp_sup:start_child(Arg). split([{module, M} | Opts]) -> @@ -196,7 +197,7 @@ init(T) -> %% i/1 %% A transport process. -i({T, Ref, Mod, Pid, Opts, Addrs}) +i({T, Ref, Mod, Pid, Opts, Addrs, SPid}) when T == accept; T == connect -> monitor(process, Pid), @@ -214,7 +215,7 @@ i({T, Ref, Mod, Pid, Opts, Addrs}) ?DEFAULT_FRAGMENT_TIMEOUT), ?IS_TIMEOUT(Tmo) orelse ?ERROR({fragment_timer, Tmo}), Throttle = proplists:get_value(throttle_cb, OwnOpts, false), - Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs), + Sock = init(T, Ref, Mod, Pid, SslOpts, Rest, Addrs, SPid), MPid ! {stop, self()}, %% tell the monitor to die M = if SslOpts -> ssl; true -> Mod end, putr(?REF_KEY, Ref), @@ -228,6 +229,11 @@ i({T, Ref, Mod, Pid, Opts, Addrs}) %% Put the reference in the process dictionary since we now use it %% advertise the ssl socket after TLS upgrade. +i({T, _Ref, _Mod, _Pid, _Opts, _Addrs} = Arg) %% from old code + when T == accept; + T == connect -> + i(erlang:append_element(Arg, _SPid = false)); + %% A monitor process to kill the transport if the parent dies. i(#monitor{parent = Pid, transport = TPid} = S) -> proc_lib:init_ack({ok, self()}), @@ -240,16 +246,18 @@ i(#monitor{parent = Pid, transport = TPid} = S) -> %% death. However, a link can be unlinked and this is exactly what %% gen_tcp seems to so. Links should be left to supervisors. -i({listen, LRef, APid, {Mod, Opts, Addrs}}) -> - [_] = diameter_config:subscribe(LRef, transport), %% assert existence +i({listen = L, Ref, _APid, T}) -> %% from old code + i({L, Ref, T}); + +i({listen, Ref, {Mod, Opts, Addrs}}) -> + [_] = diameter_config:subscribe(Ref, transport), %% assert existence {[LA, LP], Rest} = proplists:split(Opts, [ip, port]), LAddrOpt = get_addr(LA, Addrs), LPort = get_port(LP), {ok, LSock} = Mod:listen(LPort, gen_opts(LAddrOpt, Rest)), LAddr = laddr(LAddrOpt, Mod, LSock), - true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}), + true = diameter_reg:add_new({?MODULE, listener, {Ref, {LAddr, LSock}}}), proc_lib:init_ack({ok, self(), {LAddr, LSock}}), - monitor(process, APid), #listener{socket = LSock}. laddr([], Mod, Sock) -> @@ -268,21 +276,22 @@ ssl_opts([{ssl_options, Opts}]) ssl_opts(T) -> ?ERROR({ssl_options, T}). -%% init/7 +%% init/8 %% Establish a TLS connection before capabilities exchange ... -init(Type, Ref, Mod, Pid, true, Opts, Addrs) -> - init(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs); +init(Type, Ref, Mod, Pid, true, Opts, Addrs, SPid) -> + init(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs, SPid); %% ... or not. -init(Type, Ref, Mod, Pid, _, Opts, Addrs) -> - init(Type, Ref, Mod, Pid, Opts, Addrs). +init(Type, Ref, Mod, Pid, _, Opts, Addrs, SPid) -> + init(Type, Ref, Mod, Pid, Opts, Addrs, SPid). -%% init/6 +%% init/7 -init(accept = T, Ref, Mod, Pid, Opts, Addrs) -> +init(accept = T, Ref, Mod, Pid, Opts, Addrs, SPid) -> {[Matches], Rest} = proplists:split(Opts, [accept]), - {LAddr, LSock} = listener(Ref, {Mod, Rest, Addrs}), + {ok, LPid, {LAddr, LSock}} = listener(Ref, {Mod, Rest, Addrs}), + ok = gen_server:call(LPid, {accept, SPid}, infinity), proc_lib:init_ack({ok, self(), [LAddr]}), Sock = ok(accept(Mod, LSock)), ok = accept_peer(Mod, Sock, accept(Matches)), @@ -290,7 +299,7 @@ init(accept = T, Ref, Mod, Pid, Opts, Addrs) -> diameter_peer:up(Pid), Sock; -init(connect = T, Ref, Mod, Pid, Opts, Addrs) -> +init(connect = T, Ref, Mod, Pid, Opts, Addrs, _SPid) -> {[LA, RA, RP], Rest} = proplists:split(Opts, [ip, raddr, rport]), LAddrOpt = get_addr(LA, Addrs), RAddr = get_addr(RA), @@ -344,24 +353,26 @@ accept(Opts) -> %% Accepting processes can be started concurrently: ensure only one %% listener is started. -listener(LRef, T) -> - diameter_sync:call({?MODULE, listener, LRef}, - {?MODULE, listener, [{LRef, T, self()}]}, +listener(Ref, T) -> + diameter_sync:call({?MODULE, listener, Ref}, + {?MODULE, listener, [{Ref, T, self()}]}, infinity, infinity). -listener({LRef, T, TPid}) -> - l(diameter_reg:match({?MODULE, listener, {LRef, '_'}}), LRef, T, TPid). +%% listener/1 + +listener({Ref, T, _TPid}) -> + l(diameter_reg:match({?MODULE, listener, {Ref, '_'}}), Ref, T). + +%% l/3 %% Existing listening process ... -l([{{?MODULE, listener, {_, AS}}, LPid}], _, _, TPid) -> - LPid ! {accept, TPid}, - AS; +l([{{?MODULE, listener, {_, AS}}, LPid}], _, _) -> + {ok, LPid, AS}; %% ... or not. -l([], LRef, T, TPid) -> - {ok, _, AS} = diameter_tcp_sup:start_child({listen, LRef, TPid, T}), - AS. +l([], Ref, T) -> + diameter_tcp_sup:start_child({listen, Ref, T}). %% get_addr/1 @@ -440,6 +451,14 @@ portnr(Sock) -> %% # handle_call/3 %% --------------------------------------------------------------------------- +handle_call({accept, SPid}, _From, #listener{service = P} = S) -> + {reply, ok, if not is_pid(P), is_pid(SPid) -> + monitor(process, SPid), + S#listener{service = SPid}; + true -> + S + end}; + handle_call(_, _, State) -> {reply, nok, State}. @@ -507,19 +526,20 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid, %% %% Transition listener state. -%% An accepting transport is attaching. -l({accept, TPid}, #listener{count = N} = S) -> - monitor(process, TPid), - S#listener{count = N+1}; - -%% Accepting process has died. -l({'DOWN', _, process, _, _}, #listener{count = N} = S) -> - S#listener{count = N-1}; +%% Service process has died. +l({'DOWN', _, process, Pid, _} = T, #listener{service = Pid, + socket = Sock}) -> + gen_tcp:close(Sock), + x(T); %% Transport has been removed. l({transport, remove, _} = T, #listener{socket = Sock}) -> gen_tcp:close(Sock), - x(T). + x(T); + +%% Possibly death of an accepting process monitored in old code. +l(_, S) -> + S. %% t/2 %% diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 6f3a4801ee..4c82d4dee2 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -248,17 +248,14 @@ all() -> groups() -> Ts = tc(), Sctp = ?util:have_sctp(), - [{?util:name([R,D,A,C]), [parallel], Ts} || R <- ?ENCODINGS, - D <- ?RFCS, - A <- ?ENCODINGS, - C <- ?CONTAINERS] + [{B, [P], Ts} || {B,P} <- [{true, shuffle}, {false, parallel}]] ++ [{?util:name([T,R,D,A,C,SD,CD]), [], [start_services, add_transports, result_codes, - {group, ?util:name([R,D,A,C])}, + {group, SD orelse CD}, remove_transports, stop_services]} || T <- ?TRANSPORTS, diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 37fcbbc267..cca28dd23c 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -195,13 +195,21 @@ unique_string() -> %% have_sctp/0 have_sctp() -> - case gen_sctp:open() of - {ok, Sock} -> - gen_sctp:close(Sock), - true; - {error, E} when E == eprotonosupport; - E == esocktnosupport -> %% fail on any other reason - false + case erlang:system_info(system_architecture) of + %% We do not support the sctp version present in solaris + %% version "sparc-sun-solaris2.10", that behaves differently + %% from later versions and linux + "sparc-sun-solaris2.10" -> + false; + _-> + case gen_sctp:open() of + {ok, Sock} -> + gen_sctp:close(Sock), + true; + {error, E} when E == eprotonosupport; + E == esocktnosupport -> %% fail on any other reason + false + end end. %% --------------------------------------------------------------------------- diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index cb750c69a3..23219950bb 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -17,5 +17,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.12 +DIAMETER_VSN = 1.12.1 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl index e7a4c36ca4..2d6cb04a6d 100644 --- a/lib/edoc/src/edoc_extract.erl +++ b/lib/edoc/src/edoc_extract.erl @@ -32,9 +32,9 @@ %% %% @headerfile "edoc.hrl" (disabled until it can be made private) -include("edoc.hrl"). -%% @type filename() = file:filename(). -%% @type proplist() = proplists:property(). -%% @type syntaxTree() = erl_syntax:syntaxTree(). +%% @type filename() = //kernel/file:filename(). +%% @type proplist() = //stdlib/proplists:property(). +%% @type syntaxTree() = //syntax_tools/erl_syntax:syntaxTree(). %% @spec source(File::filename(), Env::edoc_env(), Options::proplist()) %% -> {ModuleName, edoc:edoc_module()} @@ -639,11 +639,11 @@ file_macros(_Context, Env) -> %% %% The idea is to mimic how the @type tag works. %% Using @type: -%% @type t() = t1(). Some docs of t/0; -%% Further docs of t/0. +%%```@type t() = t1(). Some docs of t/0; +%% Further docs of t/0.''' %% The same thing using -type: -%% -type t() :: t1(). % Some docs of t/0; -%% Further docs of t/0. +%%```-type t() :: t1(). % Some docs of t/0; +%% Further docs of t/0.''' find_type_docs(Forms0, Comments, Env, File) -> Tree = erl_recomment:recomment_forms(Forms0, Comments), Forms = preprocess_forms(Tree), diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl index dcc239f6b4..cc0a8d0b94 100644 --- a/lib/edoc/src/edoc_lib.erl +++ b/lib/edoc/src/edoc_lib.erl @@ -935,7 +935,7 @@ get_doc_env(Opts) -> %% Modules = [atom()] %% proplist() = [term()] %% -%% @type proplist() = proplists:property(). +%% @type proplist() = //stdlib/proplists:property(). %% @type edoc_env(). Environment information needed by EDoc for %% generating references. The data representation is not documented. %% diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl index 9e2e41e902..93f423b906 100644 --- a/lib/edoc/src/edoc_tags.erl +++ b/lib/edoc/src/edoc_tags.erl @@ -213,8 +213,10 @@ filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) -> true -> filter_tags(Ts, Tags, Where, [T | Ts1]); false -> - [warning(L, Where, "tag @~s not recognized.", [N]) || - Where =/= no], + case Where of + no -> ok; + _ -> warning(L, Where, "tag @~s not recognized.", [N]) + end, filter_tags(Ts, Tags, Where, Ts1) end; filter_tags([], _, _, Ts) -> @@ -451,7 +453,7 @@ check_type(#tag{line = L, data = Data}, P0, Ls, Ts) -> check_type(#t_def{type = Type}, P, Ls, Ts) -> check_type(Type, P, Ls, Ts); check_type(#t_type{name = Name, args = Args}, P, Ls, Ts) -> - _ = check_used_type(Name, Args, P, Ls), + check_used_type(Name, Args, P, Ls), check_types3(Args++Ts, P, Ls); check_type(#t_var{}, P, Ls, Ts) -> check_types3(Ts, P, Ls); @@ -503,7 +505,8 @@ check_used_type(#t_name{name = N, module = Mod}=Name, Args, P, LocalTypes) -> false -> #parms{warn = W, line = L, file = File} = P, %% true = ets:insert(DT, TypeName), - [type_warning(L, File, "missing type", N, NArgs) || W] + _ = [type_warning(L, File, "missing type", N, NArgs) || W], + ok end. type_warning(Line, File, S, N, NArgs) -> diff --git a/lib/erl_docgen/priv/bin/xref_mod_app.escript b/lib/erl_docgen/priv/bin/xref_mod_app.escript index ac4278bf22..4a418fe144 100755 --- a/lib/erl_docgen/priv/bin/xref_mod_app.escript +++ b/lib/erl_docgen/priv/bin/xref_mod_app.escript @@ -84,7 +84,7 @@ preloaded(TopDir) -> %% It's OK if too much data is generated as long as all applications %% and all modules are mentioned. appmods(D) -> - ErlFiles = filelib:wildcard(filename:join([D,"src","*.erl"])), + ErlFiles = filelib:wildcard(filename:join([D,"src","**","*.erl"])), AppV = filename:basename(D), App = case string:rstr(AppV, "-") of 0 -> AppV; diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd index 835407520a..615b88b61a 100644 --- a/lib/erl_docgen/priv/dtd/erlref.dtd +++ b/lib/erl_docgen/priv/dtd/erlref.dtd @@ -29,7 +29,7 @@ <!-- `name' is used in common.refs.dtd and must therefore be defined in each *ref. dtd --> -<!ELEMENT name (#PCDATA) > +<!ELEMENT name (#PCDATA|seealso)* > <!ATTLIST name name CDATA #IMPLIED arity CDATA #IMPLIED clause_i CDATA #IMPLIED diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index fe6c7d3c28..edab8e1c7e 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -2007,10 +2007,10 @@ </xsl:variable> <xsl:choose> <xsl:when test="ancestor::datatype"> - <a name="type-{$fname}"><span class="bold_code"><xsl:value-of select="."/></span></a><br/> + <a name="type-{$fname}"></a><span class="bold_code"><xsl:apply-templates/></span><br/> </xsl:when> <xsl:otherwise> - <a name="{$fname}-{$arity}"><span class="bold_code"><xsl:value-of select="."/></span></a><br/> + <a name="{$fname}-{$arity}"></a><span class="bold_code"><xsl:apply-templates/></span><br/> </xsl:otherwise> </xsl:choose> </xsl:when> diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl index 0ac7985a48..4f3af1f767 100644 --- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl +++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2015. All Rights Reserved. +%% Copyright Ericsson AB 2001-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -563,7 +563,14 @@ otp_xmlify_a_fileref(FileRef1, AppS) -> true -> case split(Marker0, "-") of [Func,Arity] -> - Func++"/"++Arity; + try list_to_integer(Arity) of + _ -> + Func++"/"++Arity + catch + _:_ -> + %% This is "type-"<a-type>. + Marker0 + end; _ -> Marker0 end @@ -831,19 +838,49 @@ local_types([]) -> []; local_types(Es) -> local_defs2(get_elem(localdef, Es)). +-define(LOCAL_TYPES, edoc_local_defs). + local_defs2([]) -> []; local_defs2(Es) -> + case collect_local_types(Es) of + [] -> local_defs3(Es); + LocalTypes -> + ?LOCAL_TYPES = ets:new(?LOCAL_TYPES, [named_table]), + true = ets:insert(?LOCAL_TYPES, LocalTypes), + try + local_defs3(Es) + after + ets:delete(?LOCAL_TYPES) + end + end. + +local_defs3(Es) -> {type,[?NL | [{v, localdef2(E)} || E <- Es]]}. +%% Does not work well for parametrized types. +collect_local_types(Es) -> + lists:append([collect_local_type(E) || E <- Es]). + +collect_local_type(#xmlElement{content = Es}) -> + case get_elem(typevar, Es) of + [] -> + [{t_abstype(get_content(abstype, Es))}]; + [_] -> + [] + end. + %% Like localdef/1, but does not use label_anchor/2 -- we don't want any %% markers or em tags in <v> tag, plain text only! +%% When used stand-alone, EDoc generates links to local types. An ETS +%% table holds local types, to avoid generating links to them. localdef2(#xmlElement{content = Es}) -> - case get_elem(typevar, Es) of - [] -> - t_utype(get_elem(type, Es)); - [V] -> - t_var(V) ++ [" = "] ++ t_utype(get_elem(type, Es)) - end. + Var = case get_elem(typevar, Es) of + [] -> + [t_abstype(get_content(abstype, Es))]; + [V] -> + t_var(V) + end, + Var ++ [" = "] ++ t_utype(get_elem(type, Es)). type_name(#xmlElement{content = Es}) -> t_name(get_elem(erlangName, get_content(typedef, Es))). @@ -855,10 +892,9 @@ types(Ts) -> typedecl(Name, #xmlElement{content = Es}) -> TypedefEs = get_content(typedef, Es), Id = "type-"++Name, - [{tag, typedef(TypedefEs)}, + [{tag, [{marker,[{id,Id}],[]}] ++ typedef(TypedefEs)}, ?NL, - {item, [{marker,[{id,Id}],[]} | - local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)]}, + {item, local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)}, ?NL]. typedef(Es) -> @@ -866,14 +902,14 @@ typedef(Es) -> ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])), case get_elem(type, Es) of [] -> - [{tt, Name}]; + Name; Type -> - [{tt, Name ++ [" = "] ++ t_utype(Type)}] + Name ++ [" = "] ++ t_utype(Type) end. -local_defs([]) -> []; +local_defs([]) -> [{p,[]}]; local_defs(Es) -> - [?NL, {ul, [{li, [{tt, localdef(E)}]} || E <- Es]}]. + [?NL, {ul, [{li, [{p, localdef(E)}]} || E <- Es]}]. localdef(E = #xmlElement{content = Es}) -> Var = case get_elem(typevar, Es) of @@ -917,6 +953,7 @@ seealso_module(Es) -> Es1 -> {section,[{title,["See also"]},{p,seq(fun see/1, Es1, [])}]} end. + seealso_function(Es) -> case get_elem(see, Es) of [] -> []; @@ -988,7 +1025,14 @@ t_name([E]) -> end. t_utype([E]) -> - t_utype_elem(E). + flatten_type(t_utype_elem(E)). + +%% Make sure see also are top elements of lists. +flatten_type(T) -> + [case is_integer(E) of + true -> [E]; + false -> E + end || E <- lists:flatten(T)]. t_utype_elem(E=#xmlElement{content = Es}) -> case get_attrval(name, E) of @@ -1021,16 +1065,14 @@ t_type([#xmlElement{name = tuple, content = Es}]) -> t_tuple(Es); t_type([#xmlElement{name = 'fun', content = Es}]) -> t_fun(Es); -t_type([#xmlElement{name = abstype, content = Es}]) -> - t_abstype(Es); +t_type([E = #xmlElement{name = abstype, content = Es}]) -> + t_abstype(E, Es); t_type([#xmlElement{name = union, content = Es}]) -> t_union(Es); t_type([#xmlElement{name = record, content = Es}]) -> t_record(Es); t_type([#xmlElement{name = map, content = Es}]) -> - t_map(Es); -t_type([#xmlElement{name = map_field, content = Es}]) -> - t_map_field(Es). + t_map(Es). t_var(E) -> [get_attrval(name, E)]. @@ -1065,35 +1107,53 @@ t_fun(Es) -> t_record([E|Es]) -> ["#", get_attrval(value, E), "{"++ seq(fun t_field/1, Es) ++"}"]. + t_field(#xmlElement{name=field, content=[Atom,Type]}) -> [get_attrval(value, Atom), "="] ++ t_utype_elem(Type). t_map(Es) -> - ["#{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). + ["#{"] ++ seq(fun t_map_field/1, Es, ["}"]). + +t_map_field(E = #xmlElement{name = map_field, content = [K,V]}) -> + KElem = t_utype_elem(K), + VElem = t_utype_elem(V), + AS = case get_attrval(assoc_type, E) of + "assoc" -> " => "; + "exact" -> " := " + end, + [KElem ++ AS ++ VElem]. -t_map_field([K,V]) -> - [t_utype_elem(K) ++ " => " ++ t_utype_elem(V)]. +t_abstype(E, Es) -> + see_type(E, t_abstype(Es)). t_abstype(Es) -> - case split_at_colon(t_name(get_elem(erlangName, Es)),[]) of - {Mod,Type} -> - [Type, "("] ++ - seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) ++ - [" (see module ", Mod, ")"]; - Type -> - [Type, "("] ++ - seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) + Name = t_name(get_elem(erlangName, Es)), + [Name, "("] ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"]). + +see_type(E, Es0) -> + case get_attrval(href, E) of + [] -> Es0; + Href0 -> + try + false = is_local_type(Es0), + %% Fails for parametrized types: + Text = #xmlText{value = lists:append(Es0)}, + {Href, Es} = otp_xmlify_a_href(Href0, [Text]), + [{seealso, [{marker, Href}], Es}] + catch + _:_ -> + Es0 + end end. -%% Split at one colon, but not at two (or more) -split_at_colon([$:,$:|_]=Rest,Acc) -> - lists:reverse(Acc)++Rest; -split_at_colon([$:|Type],Acc) -> - {lists:reverse(Acc),Type}; -split_at_colon([Char|Rest],Acc) -> - split_at_colon(Rest,[Char|Acc]); -split_at_colon([],Acc) -> - lists:reverse(Acc). +is_local_type(Es) -> + try + [_] = ets:lookup(?LOCAL_TYPES, Es), + true + catch + _:_-> + false + end. t_union(Es) -> seq(fun t_utype_elem/1, Es, " | ", []). diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index 6dc51adee1..624100ad49 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -1708,28 +1708,36 @@ error: static int get_home(char *buf, int size) { - char* homedrive; - char* homepath; - #ifdef __WIN32__ - homedrive = getenv("HOMEDRIVE"); - homepath = getenv("HOMEPATH"); -#else - homedrive = ""; - homepath = getenv("HOME"); -#endif + char* homedrive = getenv("HOMEDRIVE"); + char* homepath = getenv("HOMEPATH"); - if (!homedrive || !homepath) { - buf[0] = '.'; - buf[1] = '\0'; - return 1; - } else if (strlen(homedrive)+strlen(homepath) < size-1) { + if (homedrive && homepath) { + if (strlen(homedrive)+strlen(homepath) >= size) + return 0; strcpy(buf, homedrive); strcat(buf, homepath); return 1; } - - return 0; + else { + int len = GetWindowsDirectory(buf, size); + if (len) { + return (len < size); + } + } +#else + char* homepath = getenv("HOME"); + if (homepath) { + if (strlen(homepath) >= size) + return 0; + strcpy(buf, homepath); + return 1; + } +#endif + + buf[0] = '.'; + buf[1] = '\0'; + return 1; } diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h index fbb61b0ccf..bded811a35 100644 --- a/lib/erl_interface/src/misc/ei_portio.h +++ b/lib/erl_interface/src/misc/ei_portio.h @@ -21,7 +21,7 @@ */ #ifndef _EI_PORTIO_H #define _EI_PORTIO_H -#if !defined(__WIN32__) || !defined(VXWORKS) +#if !defined(__WIN32__) && !defined(VXWORKS) #ifdef HAVE_WRITEV /* Declaration of struct iovec *iov should be visible in this scope. */ #include <sys/uio.h> diff --git a/lib/gs/doc/src/Makefile b/lib/gs/doc/src/Makefile index b270bc84fe..a24ec77e31 100644 --- a/lib/gs/doc/src/Makefile +++ b/lib/gs/doc/src/Makefile @@ -148,7 +148,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" diff --git a/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl b/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl index 5451f1fe7d..b1f7bd7572 100644 --- a/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl +++ b/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl @@ -87,22 +87,29 @@ do_fp_unop(I, TempMap) -> %%% Fix an fmove op. do_fmove(I, TempMap) -> #fmove{src=Src,dst=Dst} = I, - case is_mem_opnd(Dst, TempMap) and is_mem_opnd(Src, TempMap) of + case + (is_mem_opnd(Src, TempMap) andalso is_mem_opnd(Dst, TempMap)) + orelse (is_mem_opnd(Src, TempMap) andalso (not is_float_temp(Dst))) + orelse ((not is_float_temp(Src)) andalso is_mem_opnd(Dst, TempMap)) + of true -> - Tmp = clone(Src), + Tmp = spill_temp(double), {[#fmove{src=Src, dst=Tmp},I#fmove{src=Tmp,dst=Dst}], true}; false -> {[I], false} end. +is_float_temp(#x86_temp{type=Type}) -> Type =:= double; +is_float_temp(#x86_mem{}) -> false. + %%% Check if an operand denotes a memory cell (mem or pseudo). is_mem_opnd(Opnd, TempMap) -> R = case Opnd of #x86_mem{} -> true; - #x86_temp{} -> + #x86_temp{type=double} -> Reg = hipe_x86:temp_reg(Opnd), case hipe_x86:temp_is_allocatable(Opnd) of true -> @@ -176,6 +183,9 @@ clone(Dst) -> #x86_mem{} -> hipe_x86:mem_type(Dst); #x86_temp{} -> hipe_x86:temp_type(Dst) end, + spill_temp(Type). + +spill_temp(Type) -> hipe_x86:mk_new_temp(Type). %%% Make a certain reg into a clone of Dst diff --git a/lib/hipe/arm/hipe_arm_registers.erl b/lib/hipe/arm/hipe_arm_registers.erl index 24cd929d41..dcf039676b 100644 --- a/lib/hipe/arm/hipe_arm_registers.erl +++ b/lib/hipe/arm/hipe_arm_registers.erl @@ -67,6 +67,8 @@ -define(R15, 15). -define(LAST_PRECOLOURED, 15). % must handle both GPR and FPR ranges +-define(LR, ?R14). + -define(ARG0, ?R1). -define(ARG1, ?R2). -define(ARG2, ?R3). @@ -114,7 +116,7 @@ stack_pointer() -> ?STACK_POINTER. proc_pointer() -> ?PROC_POINTER. -lr() -> ?R14. +lr() -> ?LR. pc() -> ?R15. @@ -198,7 +200,9 @@ call_clobbered() -> % does the RA strip the type or not? ]. tailcall_clobbered() -> % tailcall crapola needs one temp - [{?TEMP1,tagged},{?TEMP1,untagged}]. + [{?TEMP1,tagged},{?TEMP1,untagged} + ,{?LR,tagged},{?LR,untagged} + ]. live_at_return() -> [%%{?LR,untagged}, diff --git a/lib/hipe/arm/hipe_rtl_to_arm.erl b/lib/hipe/arm/hipe_rtl_to_arm.erl index ad5a559995..93342aba33 100644 --- a/lib/hipe/arm/hipe_rtl_to_arm.erl +++ b/lib/hipe/arm/hipe_rtl_to_arm.erl @@ -148,10 +148,11 @@ mk_shift_ir(S, Dst, Src1, ShiftOp, Src2) -> mk_li(Tmp, Src1, mk_shift_rr(S, Dst, Tmp, ShiftOp, Src2)). -mk_shift_ri(S, Dst, Src1, ShiftOp, Src2) when is_integer(Src2) -> - if Src2 >= 0, Src2 < 32 -> ok; - true -> io:format("~w: excessive immediate shift ~w\n", [?MODULE,Src2]) - end, +mk_shift_ri(S, Dst, Src1, ShiftOp, 0) + when ShiftOp =:= lsl; ShiftOp =:= lsr; ShiftOp =:= asr -> + [hipe_arm:mk_move(S, Dst, Src1)]; +mk_shift_ri(S, Dst, Src1, ShiftOp, Src2) + when is_integer(Src2), Src2 > 0, Src2 < 32 -> Am1 = {Src1,ShiftOp,Src2}, [hipe_arm:mk_move(S, Dst, Am1)]. diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl index 9453ca6c6f..230fce2e68 100644 --- a/lib/hipe/cerl/erl_bif_types.erl +++ b/lib/hipe/cerl/erl_bif_types.erl @@ -124,7 +124,7 @@ t_map_entries/2, t_map_put/3, t_map_update/3, - map_pairwise_merge/3 + t_map_pairwise_merge/4 ]). -ifdef(DO_ERL_BIF_TYPES_TEST). @@ -1689,10 +1689,10 @@ type(maps, merge, 2, Xs, Opaques) -> BDefK = t_map_def_key(MapB, Opaques), ADefV = t_map_def_val(MapA, Opaques), BDefV = t_map_def_val(MapB, Opaques), - t_map(map_pairwise_merge( + t_map(t_map_pairwise_merge( fun(K, _, _, mandatory, V) -> {K, mandatory, V}; (K, MNess, VA, optional, VB) -> {K, MNess, t_sup(VA,VB)} - end, MapA, MapB), + end, MapA, MapB, Opaques), t_sup(ADefK, BDefK), t_sup(ADefV, BDefV)) end, Opaques); type(maps, put, 3, Xs, Opaques) -> diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 243bb6e25d..15f7b793a1 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -159,6 +159,7 @@ t_map_get/2, t_map_get/3, t_map_is_key/2, t_map_is_key/3, t_map_update/2, t_map_update/3, + t_map_pairwise_merge/4, t_map_put/2, t_map_put/3, t_matchstate/0, t_matchstate/2, @@ -219,8 +220,7 @@ is_erl_type/1, atom_to_string/1, var_table__new/0, - cache__new/0, - map_pairwise_merge/3 + cache__new/0 ]). %%-define(DO_ERL_TYPES_TEST, true). @@ -494,9 +494,9 @@ t_contains_opaque(?function(Domain, Range), Opaques) -> t_contains_opaque(Domain, Opaques) orelse t_contains_opaque(Range, Opaques); t_contains_opaque(?identifier(_Types), _Opaques) -> false; -t_contains_opaque(?integer(_Types), _Opaques) -> false; t_contains_opaque(?int_range(_From, _To), _Opaques) -> false; t_contains_opaque(?int_set(_Set), _Opaques) -> false; +t_contains_opaque(?integer(_Types), _Opaques) -> false; t_contains_opaque(?list(Type, Tail, _), Opaques) -> t_contains_opaque(Type, Opaques) orelse t_contains_opaque(Tail, Opaques); t_contains_opaque(?map(_, _, _) = Map, Opaques) -> @@ -1768,13 +1768,26 @@ mapdict_insert(E1={K1,_,_}, [E2={K2,_,_}|T]) when K1 > K2 -> [E2|mapdict_insert(E1, T)]; mapdict_insert(E={_,_,_}, T) -> [E|T]. +-type map_pairwise_merge_fun() :: fun((erl_type(), + t_map_mandatoriness(), erl_type(), + t_map_mandatoriness(), erl_type()) + -> t_map_pair() | false). + +-spec t_map_pairwise_merge(map_pairwise_merge_fun(), erl_type(), erl_type(), + opaques()) -> t_map_dict(). +t_map_pairwise_merge(F, MapA, MapB, Opaques) -> + do_opaque(MapA, Opaques, + fun(UMapA) -> + do_opaque(MapB, Opaques, + fun(UMapB) -> + map_pairwise_merge(F, UMapA, UMapB) + end) + end). + %% Merges the pairs of two maps together. Missing pairs become (?opt, DefV) or %% (?opt, ?none), depending on whether K \in DefK. --spec map_pairwise_merge(fun((erl_type(), - t_map_mandatoriness(), erl_type(), - t_map_mandatoriness(), erl_type()) - -> t_map_pair() | false), - erl_type(), erl_type()) -> t_map_dict(). +-spec map_pairwise_merge(map_pairwise_merge_fun(), erl_type(), erl_type()) + -> t_map_dict(). map_pairwise_merge(F, ?map(APairs, ADefK, ADefV), ?map(BPairs, BDefK, BDefV)) -> map_pairwise_merge(F, APairs, ADefK, ADefV, BPairs, BDefK, BDefV). @@ -4749,7 +4762,7 @@ type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames, S, D, L, C) -> {Rep, L2, C2} = recur_limit(Fun, D, L1, TypeName, TypeNames), Rep1 = choose_opaque_type(Rep, Type), Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of - true -> Rep1; + true -> Rep; false -> ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), t_opaque(Module, Name, ArgTypes2, Rep1) @@ -4821,7 +4834,7 @@ remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, RemType, TypeNames, NewRep1 = choose_opaque_type(NewRep, Type), NewRep2 = case cannot_have_opaque(NewRep1, RemType, TypeNames) of - true -> NewRep1; + true -> NewRep; false -> ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), t_opaque(Mod, Name, ArgTypes2, NewRep1) diff --git a/lib/hipe/ppc/hipe_ppc.erl b/lib/hipe/ppc/hipe_ppc.erl index 0fa96162f6..380e791bc1 100644 --- a/lib/hipe/ppc/hipe_ppc.erl +++ b/lib/hipe/ppc/hipe_ppc.erl @@ -167,8 +167,10 @@ temp_is_precoloured(#ppc_temp{reg=Reg,type=Type}) -> _ -> hipe_ppc_registers:is_precoloured_gpr(Reg) end. -mk_simm16(Value) -> #ppc_simm16{value=Value}. -mk_uimm16(Value) -> #ppc_uimm16{value=Value}. +mk_simm16(Value) when Value >= -(1 bsl 15), Value < (1 bsl 15) -> + #ppc_simm16{value=Value}. +mk_uimm16(Value) when Value >= 0, Value < (1 bsl 16) -> + #ppc_uimm16{value=Value}. mk_mfa(M, F, A) -> #ppc_mfa{m=M, f=F, a=A}. @@ -240,7 +242,11 @@ mk_li(Dst, Value, Tail) -> % Dst can be R0 Value =< 16#7FFFFFFF -> mk_li32(Dst, R0, Value, Tail); true -> - Highest = (Value bsr 48), % Value@highest + Highest = case (Value bsr 48) of % Value@highest + TopBitSet when TopBitSet >= (1 bsl 15) -> + TopBitSet - (1 bsl 16); % encoder needs it to be negative + FitsSimm16 -> FitsSimm16 + end, Higher = (Value bsr 32) band 16#FFFF, % Value@higher High = (Value bsr 16) band 16#FFFF, % Value@h Low = Value band 16#FFFF, % Value@l diff --git a/lib/hipe/ppc/hipe_ppc_assemble.erl b/lib/hipe/ppc/hipe_ppc_assemble.erl index ff9da01b11..d89ff6235c 100644 --- a/lib/hipe/ppc/hipe_ppc_assemble.erl +++ b/lib/hipe/ppc/hipe_ppc_assemble.erl @@ -175,7 +175,8 @@ do_slwi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 32 -> {Dst, Src1, {sh,N}, {mb,0}, {me,31-N}}. do_srwi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 32 -> - {Dst, Src1, {sh,32-N}, {mb,N}, {me,31}}. + %% SH should be 0 (not 32) when N is 0 + {Dst, Src1, {sh,(32-N) band 31}, {mb,N}, {me,31}}. do_srawi_src2({uimm,N}) when is_integer(N), 0 =< N, N < 32 -> {sh,N}. @@ -184,7 +185,8 @@ do_sldi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 64 -> {Dst, Src1, {sh6,N}, {me6,63-N}}. do_srdi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 64 -> - {Dst, Src1, {sh6,64-N}, {mb6,N}}. + %% SH should be 0 (not 64) when N is 0 + {Dst, Src1, {sh6,(64-N) band 63}, {mb6,N}}. do_sradi_src2({uimm,N}) when is_integer(N), 0 =< N, N < 64 -> {sh6,N}. @@ -246,6 +248,7 @@ do_load(I) -> case LdOp of 'ld' -> do_disp_ds(Disp); 'ldu' -> do_disp_ds(Disp); + 'lwa' -> do_disp_ds(Disp); _ -> do_disp(Disp) end, NewBase = do_reg(Base), diff --git a/lib/hipe/sparc/hipe_rtl_to_sparc.erl b/lib/hipe/sparc/hipe_rtl_to_sparc.erl index eef5ba8d96..f9c043eafe 100644 --- a/lib/hipe/sparc/hipe_rtl_to_sparc.erl +++ b/lib/hipe/sparc/hipe_rtl_to_sparc.erl @@ -750,13 +750,25 @@ xaluop_commutes(XAluOp) -> xaluop_is_shift(XAluOp) -> case XAluOp of + 'add' -> false; + 'addcc' -> false; + 'and' -> false; + 'andcc' -> false; + 'cmpcc' -> false; + 'ldsb' -> false; + 'ldub' -> false; + 'lduw' -> false; + 'or' -> false; 'sll' -> true; - 'srl' -> true; + %% 'sllx' -> true; + 'smul' -> false; 'sra' -> true; - 'sllx' -> true; - 'srlx' -> true; - 'srax' -> true; - _ -> false + %% 'srax' -> true; + 'srl' -> true; + %% 'srlx' -> true; + 'sub' -> false; + 'subcc' -> false; + 'xor' -> false end. %%% Convert an extended SPARC AluOp back to a plain AluOp. @@ -764,9 +776,23 @@ xaluop_is_shift(XAluOp) -> xaluop_normalise(XAluOp) -> case XAluOp of - 'cmp' -> 'sub'; + 'add' -> 'add'; + 'addcc' -> 'addcc'; + 'and' -> 'and'; + 'andcc' -> 'andcc'; + %% 'cmp' -> 'sub'; 'cmpcc' -> 'subcc'; - _ -> XAluOp + 'ldsb' -> 'ldsb'; + 'ldub' -> 'ldub'; + 'lduw' -> 'lduw'; + 'or' -> 'or'; + 'sll' -> 'sll'; + 'smul' -> 'smul'; + 'sra' -> 'sra'; + 'srl' -> 'srl'; + 'sub' -> 'sub'; + 'subcc' -> 'subcc'; + 'xor' -> 'xor' end. %%% Convert an RTL condition code. diff --git a/lib/hipe/sparc/hipe_sparc_registers.erl b/lib/hipe/sparc/hipe_sparc_registers.erl index 884215702b..6681a10070 100644 --- a/lib/hipe/sparc/hipe_sparc_registers.erl +++ b/lib/hipe/sparc/hipe_sparc_registers.erl @@ -86,6 +86,8 @@ -define(I7, 31). -define(LAST_PRECOLOURED,31). % must handle both GRP and FPR ranges +-define(RA, ?O7). + -define(ARG0, ?O1). -define(ARG1, ?O2). -define(ARG2, ?O3). @@ -174,7 +176,7 @@ stack_pointer() -> ?STACK_POINTER. proc_pointer() -> ?PROC_POINTER. -return_address() -> ?O7. +return_address() -> ?RA. g0() -> ?G0. @@ -283,7 +285,9 @@ call_clobbered() -> % does the RA strip the type or not? ]. tailcall_clobbered() -> % tailcall crapola needs one temp - [{?TEMP1,tagged},{?TEMP1,untagged}]. + [{?TEMP1,tagged},{?TEMP1,untagged} + ,{?RA,tagged},{?RA,untagged} + ]. live_at_return() -> [{?HEAP_POINTER,untagged}, diff --git a/lib/hipe/test/hipe_testsuite_driver.erl b/lib/hipe/test/hipe_testsuite_driver.erl index 03ec7adfd0..a3048d907e 100644 --- a/lib/hipe/test/hipe_testsuite_driver.erl +++ b/lib/hipe/test/hipe_testsuite_driver.erl @@ -99,7 +99,7 @@ write_suite(Suite) -> write_header(#suite{suitename = SuiteName, outputfile = OutputFile, testcases = TestCases}) -> Exports = format_export(TestCases), - TimeLimit = 3, %% with 1 or 2 it fails on some slow machines... + TimeLimit = 5, %% with 1 or 2 it fails on some slow machines... io:format(OutputFile, "%% ATTENTION!\n" "%% This is an automatically generated file. Do not edit.\n\n" diff --git a/lib/hipe/x86/hipe_x86_postpass.erl b/lib/hipe/x86/hipe_x86_postpass.erl index 939baeccec..4515822a34 100644 --- a/lib/hipe/x86/hipe_x86_postpass.erl +++ b/lib/hipe/x86/hipe_x86_postpass.erl @@ -95,7 +95,8 @@ peep([I=#move{src=#x86_temp{reg=Src}, dst=#x86_temp{reg=Dst}}, %% ElimBinALMDouble %% ---------------- -peep([Move=#move{src=Src, dst=Dst}, Alu=#alu{src=Src, dst=Dst}|Insns], Res, Lst) -> +peep([Move=#move{src=Src, dst=Dst}, Alu=#alu{src=Src, dst=Dst}|Insns], Res, Lst) + when not is_record(Dst, x86_mem) -> peep([Alu#alu{src=Dst}|Insns], [Move|Res], [elimBinALMDouble|Lst]); diff --git a/lib/ic/doc/src/Makefile b/lib/ic/doc/src/Makefile index c9691df7af..19f12ac6b9 100644 --- a/lib/ic/doc/src/Makefile +++ b/lib/ic/doc/src/Makefile @@ -225,7 +225,7 @@ release_docs_spec: docs $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3" diff --git a/lib/ic/src/ic_codegen.erl b/lib/ic/src/ic_codegen.erl index adad021da1..a3f141f606 100644 --- a/lib/ic/src/ic_codegen.erl +++ b/lib/ic/src/ic_codegen.erl @@ -245,8 +245,8 @@ emit_stub_head(G, F1, Name, java) -> stub_header(G, Name) -> ["Implementation stub file", "", - io_lib:format("Target: ~s", [Name]), - io_lib:format("Source: ~s", [ic_genobj:idlfile(G)]), + io_lib:format("Target: ~ts", [Name]), + io_lib:format("Source: ~ts", [ic_genobj:idlfile(G)]), io_lib:format("IC vsn: ~s", [?COMPILERVSN]), "", "This file is automatically generated. DO NOT EDIT IT."]. @@ -298,8 +298,8 @@ emit_hrl_head(G, Fd, Name, c_server) -> hrl_header(G, Name) -> ["", - io_lib:format("Target: ~s", [Name]), - io_lib:format("Source: ~s", [ic_genobj:idlfile(G)]), + io_lib:format("Target: ~ts", [Name]), + io_lib:format("Source: ~ts", [ic_genobj:idlfile(G)]), io_lib:format("IC vsn: ~s", [?COMPILERVSN]), "", "This file is automatically generated. DO NOT EDIT IT."]. diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml index aeda961714..65b3dcde95 100644 --- a/lib/inets/doc/src/http_server.xml +++ b/lib/inets/doc/src/http_server.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2004</year><year>2015</year> + <year>2004</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -40,8 +40,8 @@ <item>Secure Sockets Layer (SSL)</item> <item>Erlang Scripting Interface (ESI)</item> <item>Common Gateway Interface (CGI)</item> - <item>User Authentication (using <c>Mnesia</c>, - <c>Dets</c> or plain text database)</item> + <item>User Authentication (using Mnesia, + Dets or plain text database)</item> <item>Common Logfile Format (with or without disk_log(3) support)</item> <item>URL Aliasing</item> <item>Action Mappings</item> @@ -563,7 +563,7 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[ <title>mod_auth - User Authentication</title> <p>The <seealso marker="mod_auth">mod_auth(3)</seealso> module provides for basic user authentication using - textual files, <c>Dets</c> databases as well as <c>Mnesia</c> databases.</p> + textual files, Dets databases as well as Mnesia databases.</p> <p>Uses the following Erlang Web Server API interaction data: </p> <list type="bulleted"> @@ -580,15 +580,15 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[ <section> <title>Mnesia As Authentication Database</title> - <p>If <c>Mnesia</c> is used as storage method, <c>Mnesia</c> must be - started before the HTTP server. The first time <c>Mnesia</c> is + <p>If Mnesia is used as storage method, Mnesia must be + started before the HTTP server. The first time Mnesia is started, the schema and the tables must be created before - <c>Mnesia</c> is started. A simple example of a module with two - functions that creates and start <c>Mnesia</c> is provided + Mnesia is started. A simple example of a module with two + functions that creates and start Mnesia is provided here. Function <c>first_start/0</c> is to be used the first time. It creates the schema and the tables. <c>start/0</c> is to be used in consecutive startups. - <c>start/0</c> starts <c>Mnesia</c> and waits for the tables to + <c>start/0</c> starts Mnesia and waits for the tables to be initiated. This function must only be used when the schema and the tables are already created.</p> @@ -616,25 +616,25 @@ start() -> mnesia:start(), mnesia:wait_for_tables([httpd_user, httpd_group], 60000). </code> - <p>To create the <c>Mnesia</c> tables, we use two records defined in + <p>To create the Mnesia tables, we use two records defined in <c>mod_auth.hrl</c>, so that file must be included. <c>first_start/0</c> creates a schema that specifies on which nodes the database is to reside. - Then it starts <c>Mnesia</c> and creates the tables. The first argument + Then it starts Mnesia and creates the tables. The first argument is the name of the tables, the second argument is a list of options of how to create the table, see - <seealso marker="mnesia:mnesia"><c>mnesia</c></seealso>, documentation for + <seealso marker="mnesia:mnesia"><c>mnesia(3)</c></seealso>, documentation for more information. As the implementation of the <c>mod_auth_mnesia</c> saves one row for each user, the type must be <c>bag</c>. When the schema and the tables are created, function <seealso marker="mnesia:mnesia#start-0">mnesia:start/0</seealso> - is used to start <c>Mnesia</c> and - waits for the tables to be loaded. <c>Mnesia</c> uses the + is used to start Mnesia and + waits for the tables to be loaded. Mnesia uses the directory specified as <c>mnesia_dir</c> at startup if specified, - otherwise <c>Mnesia</c> uses the current directory. For security - reasons, ensure that the <c>Mnesia</c> tables are stored outside + otherwise Mnesia uses the current directory. For security + reasons, ensure that the Mnesia tables are stored outside the document tree of the HTTP server. If they are placed in the directory which it protects, clients can download the tables. - Only the <c>Dets</c> and <c>Mnesia</c> storage + Only the Dets and Mnesia storage methods allow writing of dynamic user data to disk. <c>plain</c> is a read only method.</p> </section> @@ -669,7 +669,7 @@ start() -> <section> <title>mod_disk_log - Logging Using Disk_Log.</title> <p>Standard logging using the "Common Logfile Format" and - <seealso marker="kernel:disk_log">kernel:disk_log(3)</seealso>.</p> + <seealso marker="kernel:disk_log">disk_log(3)</seealso>.</p> <p>Uses the following Erlang Web Server API interaction data: </p> <list type="bulleted"> diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index ca9b268a03..13471aab2c 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2015</year> + <year>2004</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -72,7 +72,7 @@ <p><c>profile() = atom()</c></p> <p><c>path() = string()</c> representing a file path or directory path</p> <p><c>ip_address()</c> = See the - <seealso marker="kernel:inet">inet(3)</seealso> manual page in <c>Kernel</c>.</p> + <seealso marker="kernel:inet">inet(3)</seealso> manual page in Kernel.</p> <p><c>socket_opt()</c> = See the options used by <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> <c>gen_tcp(3)</c> and <seealso marker="ssl:ssl">ssl(3)</seealso> connect(s)</p> diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 62b92b8356..d74635fc01 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1997</year><year>2015</year> + <year>1997</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -455,7 +455,7 @@ text/plain asc txt</pre> directory. Several files can be given, in which case the server returns the first it finds, for example:</p> - <code>{directory_index, ["index.hml", "welcome.html"]}</code> + <code>{directory_index, ["index.html", "welcome.html"]}</code> <p>Access to http://your.server.org/docs/ would return http://your.server.org/docs/index.html or @@ -711,7 +711,7 @@ text/plain asc txt</pre> <item> <p>Sets the type of authentication database that is used for the directory. The key difference between the different methods is - that dynamic data can be saved when <c>Mnesia</c> and <c>Dets</c> + that dynamic data can be saved when Mnesia and Dets are used. This property is called <c>AuthDbType</c> in the Apache-like configuration files.</p> @@ -731,10 +731,10 @@ text/plain asc txt</pre> <code> ragnar:s7Xxv7 edward:wwjau8 </code> - <p>If the <c>Dets</c> storage method is used, the user database is - maintained by <c>Dets</c> and must not be edited by hand. Use the + <p>If the Dets storage method is used, the user database is + maintained by Dets and must not be edited by hand. Use the API functions in module <c>mod_auth</c> to create/edit the user - database. This directive is ignored if the <c>Mnesia</c> + database. This directive is ignored if the Mnesia storage method is used. For security reasons, ensure that <c>auth_user_file</c> is stored outside the document tree of the web server. If it is placed in the directory that it protects, @@ -753,10 +753,10 @@ text/plain asc txt</pre> <code>group1: bob joe ante</code> - <p>If the <c>Dets</c> storage method is used, the group database is - maintained by <c>Dets</c> and must not be edited by hand. Use the + <p>If the Dets storage method is used, the group database is + maintained by Dets and must not be edited by hand. Use the API for module <c>mod_auth</c> to create/edit the group database. - This directive is ignored if the <c>Mnesia</c> storage method is used. + This directive is ignored if the Mnesia storage method is used. For security reasons, ensure that the <c>auth_group_file</c> is stored outside the document tree of the web server. If it is placed in the directory that it protects, clients diff --git a/lib/inets/doc/src/mod_auth.xml b/lib/inets/doc/src/mod_auth.xml index 4b7088b2c5..c4f844622b 100644 --- a/lib/inets/doc/src/mod_auth.xml +++ b/lib/inets/doc/src/mod_auth.xml @@ -33,7 +33,7 @@ <modulesummary>User authentication using text files, Dets, or Mnesia database.</modulesummary> <description> <p>This module provides for basic user authentication using - textual files, <c>Dets</c> databases, or <c>Mnesia</c> databases.</p> + textual files, Dets databases, or Mnesia databases.</p> </description> <funcs> diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index e5182ca23c..9e54f2b2c5 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -395,7 +395,8 @@ validate_properties(Properties) -> %% That is, if property A depends on property B. %% The only sunch preperty at this time is bind_address that depends %% on ipfamily. -validate_properties2(Properties) -> +validate_properties2(Properties0) -> + Properties = fix_ipfamily(Properties0), case proplists:get_value(bind_address, Properties) of undefined -> case proplists:get_value(sock_type, Properties, ip_comm) of @@ -422,6 +423,15 @@ validate_properties2(Properties) -> end end. +fix_ipfamily(Properties) -> + case proplists:get_value(ipfamily, Properties) of + undefined -> + Properties; + IpFamily -> + NewProps = proplists:delete(ipfamily, Properties), + [{ipfamily, validate_ipfamily(IpFamily)} | NewProps] + end. + add_inet_defaults(Properties) -> case proplists:get_value(ipfamily, Properties) of undefined -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index d4d21f6774..3a31daeb20 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,16 +18,10 @@ %% %CopyrightEnd% {"%VSN%", [ - {<<"6.3.1">>, - [{load_module, mod_esi, soft_purge, soft_purge, []} - ]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ], [ - {<<"6.3.1">>, - [{load_module, mod_esi, soft_purge, soft_purge, []} - ]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ] diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl index 5eaf3a28a0..38b8229389 100644 --- a/lib/inets/test/inets_SUITE.erl +++ b/lib/inets/test/inets_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2015. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -212,11 +212,19 @@ start_httpd(Config) when is_list(Config) -> Pids0 = [ServicePid || {_, ServicePid} <- inets:services()], true = lists:member(Pid0, Pids0), [_|_] = inets:services_info(), - inets:stop(httpd, Pid0), ct:sleep(500), + Pids1 = [ServicePid || {_, ServicePid} <- inets:services()], + false = lists:member(Pid0, Pids1), + {ok, Pid0b} = + inets:start(httpd, [{port, 0}, {ipfamily, inet6fb4} | HttpdConf]), + Pids0b = [ServicePid || {_, ServicePid} <- inets:services()], + true = lists:member(Pid0b, Pids0b), + [_|_] = inets:services_info(), + inets:stop(httpd, Pid0b), + ct:sleep(500), Pids1 = [ServicePid || {_, ServicePid} <- inets:services()], - false = lists:member(Pid0, Pids1), + false = lists:member(Pid0b, Pids1), {ok, Pid1} = inets:start(httpd, [{port, 0}, {ipfamily, inet} | HttpdConf], stand_alone), diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index 5b8b1463c8..1e664337e6 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -33,7 +33,7 @@ suite() -> all() -> [default_tree, ftpc_worker, tftpd_worker, - httpd_subtree, httpd_subtree_profile, + httpd_config, httpd_subtree, httpd_subtree_profile, httpc_subtree]. groups() -> @@ -52,9 +52,32 @@ end_per_suite(_) -> inets:stop(), ok. -init_per_testcase(httpd_subtree, Config) -> +init_per_testcase(httpd_config = TC, Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + Dir = filename:join(PrivDir, TC), + ok = file:make_dir(Dir), + + FallbackConfig = [{port, 0}, + {server_name,"www.test"}, + {modules, [mod_get]}, + {server_root, Dir}, + {document_root, Dir}, + {bind_address, any}, + {ipfamily, inet6fb4}], + try + inets:stop(), + inets:start(), + inets:start(httpd, FallbackConfig), + Config + catch + _:Reason -> + inets:stop(), + exit({failed_starting_inets, Reason}) + end; + +init_per_testcase(httpd_subtree = TC, Config) -> PrivDir = proplists:get_value(priv_dir, Config), - Dir = filename:join(PrivDir, "root"), + Dir = filename:join(PrivDir, TC), ok = file:make_dir(Dir), SimpleConfig = [{port, 0}, @@ -75,9 +98,9 @@ init_per_testcase(httpd_subtree, Config) -> exit({failed_starting_inets, Reason}) end; -init_per_testcase(httpd_subtree_profile, Config) -> +init_per_testcase(httpd_subtree_profile = TC, Config) -> PrivDir = proplists:get_value(priv_dir, Config), - Dir = filename:join(PrivDir, "root"), + Dir = filename:join(PrivDir, TC), ok = file:make_dir(Dir), SimpleConfig = [{port, 0}, @@ -193,6 +216,11 @@ tftpd_worker(Config) when is_list(Config) -> [] = supervisor:which_children(tftp_sup), ok. +httpd_config() -> + [{doc, "Makes sure the httpd config works for inet6fb4."}]. +httpd_config(Config) when is_list(Config) -> + do_httpd_subtree(Config, default). + httpd_subtree() -> [{doc, "Makes sure the httpd sub tree is correct."}]. httpd_subtree(Config) when is_list(Config) -> diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 747724a86b..f668ef106c 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.3.2 +INETS_VSN = 6.3.3 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/doc/src/Makefile b/lib/jinterface/doc/src/Makefile index cd1e61a795..508c8e01b5 100644 --- a/lib/jinterface/doc/src/Makefile +++ b/lib/jinterface/doc/src/Makefile @@ -166,7 +166,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html/java/$(JAVA_PKG_PATH)" $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" - (/bin/cp -rf ../html "$(RELSYSDIR)/doc") + ($(CP) -rf ../html "$(RELSYSDIR)/doc") # $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \ # "$(RELSYSDIR)/doc/html" diff --git a/lib/kernel/doc/src/app.xml b/lib/kernel/doc/src/app.xml index 5e0da409a3..d2e9390d7e 100644 --- a/lib/kernel/doc/src/app.xml +++ b/lib/kernel/doc/src/app.xml @@ -151,7 +151,7 @@ ApplicationVersion = string()</code> application is allowed to be started. <c>systools</c> uses this list to generate correct start scripts. Defaults to the empty list, but notice that all applications have - dependencies to (at least) <c>Kernel</c> and <c>STDLIB</c>.</p> + dependencies to (at least) Kernel and STDLIB.</p> </item> <tag><c>env</c></tag> <item> @@ -171,7 +171,7 @@ ApplicationVersion = string()</code> implemented as a supervision tree, otherwise the application controller does not know how to start it. <c>mod</c> can be omitted for applications without processes, typically - code libraries, for example, <c>STDLIB</c>.</p> + code libraries, for example, STDLIB.</p> </item> <tag><c>start_phases</c></tag> <item> @@ -236,7 +236,7 @@ ApplicationVersion = string()</code> <section> <title>See Also</title> <p><seealso marker="application"><c>application(3)</c></seealso>, - <seealso marker="sasl:systools"><c>sasl:systools(3)</c></seealso></p> + <seealso marker="sasl:systools"><c>systools(3)</c></seealso></p> </section> </fileref> diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 8d33aa86e7..886286b76d 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -200,7 +200,7 @@ <seealso marker="app"><c>app(4)</c></seealso>.</p> <p>If <c><anno>Distributed</anno> == {<anno>Application</anno>,[<anno>Time</anno>,]<anno>Nodes</anno>}</c>, the application becomes distributed. The argument overrides - the value for the application in the <c>Kernel</c> configuration + the value for the application in the Kernel configuration parameter <c>distributed</c>. <c><anno>Application</anno></c> must be the application name (same as in the first argument). If a node crashes and <c><anno>Time</anno></c> is specified, @@ -221,7 +221,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> the application is to be started at <c>cp2@cave</c> or <c>cp3@cave</c>.</p> <p>If <c>Distributed == default</c>, the value for - the application in the <c>Kernel</c> configuration parameter + the application in the Kernel configuration parameter <c>distributed</c> is used.</p> </desc> </func> @@ -267,7 +267,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> started, <c>Application</c> is started as well.</p> <p>By default, all applications are loaded with permission <c>true</c> on all nodes. The permission can be configured - using the <c>Kernel</c> configuration parameter <c>permissions</c>.</p> + using the Kernel configuration parameter <c>permissions</c>.</p> </desc> </func> <func> diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml index 03f983b96d..5901446960 100644 --- a/lib/kernel/doc/src/auth.xml +++ b/lib/kernel/doc/src/auth.xml @@ -47,7 +47,7 @@ <desc> <p>Use <seealso marker="erts:erlang#erlang:get_cookie/0"><c>erlang:get_cookie()</c></seealso> - in <c>ERTS</c> instead.</p> + in ERTS instead.</p> </desc> </func> <func> @@ -59,7 +59,7 @@ <desc> <p>Use <seealso marker="erts:erlang#erlang:set_cookie/2"><c>erlang:set_cookie(node(), <anno>Cookie</anno>)</c> - in <c>ERTS</c></seealso> instead.</p> + in ERTS</seealso> instead.</p> </desc> </func> <func> diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml index d3611d6a03..4db377bcde 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -86,11 +86,11 @@ an <c>ebin</c> directory are ignored.</p> <p>All application directories found in the additional directories appears before the standard OTP applications, except for the - <c>Kernel</c> and <c>STDLIB</c> applications, which are placed before + Kernel and STDLIB applications, which are placed before any additional applications. In other words, modules found in any of the additional library directories override modules with - the same name in OTP, except for modules in <c>Kernel</c> and - <c>STDLIB</c>.</p> + the same name in OTP, except for modules in Kernel and + STDLIB.</p> <p>Environment variable <c>ERL_LIBS</c> (if defined) is to contain a colon-separated (for Unix-like systems) or semicolon-separated (for Windows) list of additional libraries.</p> @@ -151,7 +151,7 @@ zip:create("mnesia-4.4.7.ez", <c>$OTPROOT/lib/mnesia.ez/mnesia/ebin</c> or <c>$OTPROOT/lib/mnesia-4.4.7.ez/mnesia-4.4.7/ebin</c>.</p> - <p>The code server uses module <c>erl_prim_loader</c> in <c>ERTS</c> + <p>The code server uses module <c>erl_prim_loader</c> in ERTS (possibly through <c>erl_boot_server</c>) to read code files from archives. However, the functions in <c>erl_prim_loader</c> can also be used by other applications to read files from archives. For @@ -651,6 +651,11 @@ ok = code:finish_loading(Prepared), <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> + <note><p>As of ERTS version 9.0, a process is only considered + to be lingering in the code if it has direct references to the code. + For more information see documentation of + <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>, + which is used in order to determine this.</p></note> <p>Returns <c>true</c> if successful and any process is needed to be killed, otherwise <c>false</c>.</p> </desc> @@ -661,6 +666,11 @@ ok = code:finish_loading(Prepared), <desc> <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> + <note><p>As of ERTS version 9.0, a process is only considered + to be lingering in the code if it has direct references to the code. + For more information see documentation of + <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>, + which is used in order to determine this.</p></note> <p>Returns <c>false</c> if the module cannot be purged because of processes lingering in old code, otherwise <c>true</c>.</p> </desc> @@ -678,9 +688,9 @@ ok = code:finish_loading(Prepared), <p>Normally, <c><anno>Loaded</anno></c> is the absolute filename <c>Filename</c> from which the code is obtained. If the module is preloaded (see - <seealso marker="sasl:script"><c>sasl:script(4)</c></seealso>), + <seealso marker="sasl:script"><c>script(4)</c></seealso>), <c>Loaded==preloaded</c>. If the module is Cover-compiled (see - <seealso marker="tools:cover"><c>tools:cover(3)</c></seealso>), + <seealso marker="tools:cover"><c>cover(3)</c></seealso>), <c>Loaded==cover_compiled</c>.</p> </desc> </func> diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml index 897365f9b9..4109251387 100644 --- a/lib/kernel/doc/src/erl_boot_server.xml +++ b/lib/kernel/doc/src/erl_boot_server.xml @@ -38,13 +38,13 @@ command-line flag <c>-loader inet</c>. All hosts specified with command-line flag <c>-hosts Host</c> must have one instance of this server running.</p> - <p>This server can be started with the <c>Kernel</c> configuration + <p>This server can be started with the Kernel configuration parameter <c>start_boot_server</c>.</p> <p>The <c>erl_boot_server</c> can read regular files and files in archives. See <seealso marker="code"><c>code(3)</c></seealso> and <seealso marker="erts:erl_prim_loader"><c>erl_prim_loader(3)</c></seealso> - in <c>ERTS</c>.</p> + in ERTS.</p> <warning><p>The support for loading code from archive files is experimental. It is released before it is ready to obtain early feedback. The file format, semantics, diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml index a5ce58ef3e..75114e015c 100644 --- a/lib/kernel/doc/src/erl_ddll.xml +++ b/lib/kernel/doc/src/erl_ddll.xml @@ -201,7 +201,7 @@ <desc> <p>Removes a driver monitor in much the same way as <seealso marker="erts:erlang#erlang:demonitor/1"><c>erlang:demonitor/1</c></seealso> - in <c>ERTS</c> + in ERTS does with process monitors. For details about how to create driver monitors, see <seealso marker="#monitor/2"><c>monitor/2</c></seealso>, @@ -431,7 +431,7 @@ <p>Creates a driver monitor and works in many ways as <seealso marker="erts:erlang#erlang:monitor/2"><c>erlang:monitor/2</c></seealso> - in <c>ERTS</c>, + in ERTS, does for processes. When a driver changes state, the monitor results in a monitor message that is sent to the calling process. <c><anno>MonitorRef</anno></c> returned by this function is @@ -745,7 +745,7 @@ <p>This parameter is the name of the driver to be used in subsequent calls to function <seealso marker="erts:erlang#open_port/2"><c>erlang:open_port</c></seealso> - in <c>ERTS</c>. + in ERTS. The name can be specified as an <c>iolist()</c> or an <c>atom()</c>. The name specified when loading is used to find the object file (with the help of <c><anno>Path</anno></c> diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml index a8273e59e2..814e8eac46 100644 --- a/lib/kernel/doc/src/error_logger.xml +++ b/lib/kernel/doc/src/error_logger.xml @@ -33,7 +33,7 @@ <description> <p>The Erlang <em>error logger</em> is an event manager (see <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso> and - <seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>), + <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>), registered as <c>error_logger</c>. Errors, warnings, and info events are sent to the error logger from the Erlang runtime system and the different Erlang/OTP applications. The events are, by default, @@ -44,12 +44,12 @@ executing.</p> <p>Initially, <c>error_logger</c> has only a primitive event handler, which buffers and prints the raw event messages. During - system startup, the <c>Kernel</c> application replaces this with a + system startup, the Kernel application replaces this with a <em>standard event handler</em>, by default one that writes - nicely formatted output to the terminal. <c>Kernel</c> can also be + nicely formatted output to the terminal. Kernel can also be configured so that events are logged to a file instead, or not logged at all, see <seealso marker="kernel_app"><c>kernel(6)</c></seealso>.</p> - <p>Also the <c>SASL</c> application, if started, adds its own event + <p>Also the SASL application, if started, adds its own event handler, which by default writes supervisor, crash, and progress reports to the terminal. See <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso>.</p> @@ -58,9 +58,9 @@ User-defined event handlers can be added to handle application-specific events, see <seealso marker="#add_report_handler/1"><c>add_report_handler/1,2</c></seealso>. - Also, a useful event handler is provided in <c>STDLIB</c> for multi-file + Also, a useful event handler is provided in STDLIB for multi-file logging of events, see - <seealso marker="stdlib:log_mf_h"><c>stdlib:log_mf_h(3)</c></seealso>.</p> + <seealso marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seealso>.</p> <p>Warning events were introduced in Erlang/OTP R9C and are enabled by default as from Erlang/OTP 18.0. To retain backwards compatibility with existing user-defined event handlers, the warning events can be @@ -82,7 +82,7 @@ <p>Adds a new event handler to the error logger. The event handler must be implemented as a <c>gen_event</c> callback module, see - <seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>.</p> + <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p> <p><c><anno>Handler</anno></c> is typically the name of the callback module and <c><anno>Args</anno></c> is an optional term (defaults to []) passed to the initialization callback function <c><anno>Handler</anno>:init/1</c>. @@ -97,7 +97,7 @@ <desc> <p>Deletes an event handler from the error logger by calling <c>gen_event:delete_handler(error_logger, <anno>Handler</anno>, [])</c>, - see <seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>.</p> + see <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p> </desc> </func> <func> @@ -110,7 +110,7 @@ The <c><anno>Format</anno></c> and <c><anno>Data</anno></c> arguments are the same as the arguments of <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso> - in <c>STDLIB</c>. + in STDLIB. The event is handled by the standard event handler.</p> <p><em>Example:</em></p> <pre> @@ -171,7 +171,7 @@ ok</pre> The <c><anno>Format</anno></c> and <c><anno>Data</anno></c> arguments are the same as the arguments of <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso> - in <c>STDLIB</c>. The event is handled by the standard event handler.</p> + in STDLIB. The event is handled by the standard event handler.</p> <p><em>Example:</em></p> <pre> 1> <input>error_logger:info_msg("Something happened in ~p~n", [a_module]).</input> @@ -235,7 +235,7 @@ ok</pre> <p>Enables or disables printout of standard events to a file.</p> <p>This is done by adding or deleting the standard event handler for output to file. Thus, calling this function overrides - the value of the <c>Kernel</c> <c>error_logger</c> configuration + the value of the Kernel <c>error_logger</c> configuration parameter.</p> <p>Enabling file logging can be used together with calling <c>tty(false)</c>, to have a silent system where @@ -274,7 +274,7 @@ ok</pre> to the terminal.</p> <p>This is done by adding or deleting the standard event handler for output to the terminal. Thus, calling this function overrides - the value of the <c>Kernel</c> <c>error_logger</c> configuration parameter.</p> + the value of the Kernel <c>error_logger</c> configuration parameter.</p> </desc> </func> <func> @@ -323,7 +323,7 @@ ok</pre> The <c><anno>Format</anno></c> and <c><anno>Data</anno></c> arguments are the same as the arguments of <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso> - in <c>STDLIB</c>. + in STDLIB. The event is handled by the standard event handler. It is tagged as an error, warning, or info, see <seealso marker="#warning_map/0"><c>warning_map/0</c></seealso>.</p> @@ -416,8 +416,8 @@ ok</pre> </section> <section> <title>See Also</title> - <p><seealso marker="stdlib:gen_event"><c>stdlib:gen_event(3)</c></seealso>, - <seealso marker="stdlib:log_mf_h"><c>stdlib:log_mf_h(3)</c></seealso> + <p><seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>, + <seealso marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seealso> <seealso marker="kernel_app"><c>kernel(6)</c></seealso> <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso></p> </section> diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 7d86c3ebcb..09497482cf 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -79,7 +79,7 @@ <seealso marker="#list_dir_all"><c>list_dir_all/1</c></seealso> and <seealso marker="#read_link_all"><c>read_link_all/1</c></seealso>.</p> - <p>See also section <seealso marker="stdlib:unicode_usage#notes-about-raw-filenames">Notes About Raw Filenames</seealso> in the <c>STDLIB</c> User´s Giude.</p> + <p>See also section <seealso marker="stdlib:unicode_usage#notes-about-raw-filenames">Notes About Raw Filenames</seealso> in the STDLIB User's Guide.</p> </description> @@ -277,7 +277,7 @@ f.txt: {person, "kalle", 25}. {ok,[{person,"kalle",25},{person,"pelle",30}]}</pre> <p>The encoding of <c><anno>Filename</anno></c> can be set by a comment, as described in - <seealso marker="stdlib:epp#encoding"><c>stdlib:epp(3)</c></seealso>.</p> + <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p> </desc> </func> <func> @@ -445,7 +445,7 @@ f.txt: {person, "kalle", 25}. </taglist> <p>The encoding of <c><anno>Filename</anno></c> can be set by a comment, as described in - <seealso marker="stdlib:epp#encoding"><c>stdlib:epp(3)</c></seealso>.</p> + <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p> </desc> </func> <func> @@ -455,7 +455,7 @@ f.txt: {person, "kalle", 25}. <p>The same as <c>eval/1</c>, but the variable bindings <c><anno>Bindings</anno></c> are used in the evaluation. For information about the variable bindings, see - <seealso marker="stdlib:erl_eval"><c>stdlib:erl_eval(3)</c></seealso>.</p> + <seealso marker="stdlib:erl_eval"><c>erl_eval(3)</c></seealso>.</p> </desc> </func> <func> @@ -830,7 +830,7 @@ f.txt: {person, "kalle", 25}. this module (<c>file</c>) for reading and writing data as the interfaces provided here work with byte-oriented data. Using other (Unicode) encodings makes the - <seealso marker="stdlib:io"><c>stdlib:io(3)</c></seealso> functions + <seealso marker="stdlib:io"><c>io(3)</c></seealso> functions <c>get_chars</c>, <c>get_line</c>, and <c>put_chars</c> more suitable, as they can work with the full Unicode range.</p> <p>If data is sent to an <c>io_device()</c> in a format that cannot be @@ -847,7 +847,7 @@ f.txt: {person, "kalle", 25}. that is, <seealso marker="#read/2"><c>read/2</c></seealso> are returned "as is". If module - <seealso marker="stdlib:io"><c>stdlib:io(3)</c></seealso> is used for + <seealso marker="stdlib:io"><c>io(3)</c></seealso> is used for writing, the file can only cope with Unicode characters up to code point 255 (the ISO Latin-1 range).</p> </item> @@ -861,7 +861,7 @@ f.txt: {person, "kalle", 25}. the file lies beyond the ISO Latin-1 range (0..255), but failure occurs if the data contains Unicode code points beyond that range. The file is best read with the functions in the Unicode aware module - <seealso marker="stdlib:io"><c>stdlib:io(3)</c></seealso>.</p> + <seealso marker="stdlib:io"><c>io(3)</c></seealso>.</p> <p>Bytes written to the file by any means are translated to UTF-8 encoding before being stored on the disk file.</p> </item> @@ -891,7 +891,7 @@ f.txt: {person, "kalle", 25}. So a file can be analyzed in latin1 encoding for, for example, a BOM, positioned beyond the BOM and then be set for the right encoding before further reading. For functions identifying BOMs, see module - <seealso marker="stdlib:unicode"><c>stdlib:unicode(3)</c></seealso>. </p> + <seealso marker="stdlib:unicode"><c>unicode(3)</c></seealso>. </p> <p>This option is not allowed on <c>raw</c> files.</p> </item> <tag><c>ram</c></tag> @@ -932,7 +932,7 @@ f.txt: {person, "kalle", 25}. closed and the process itself is terminated. An <c><anno>IoDevice</anno></c> returned from this call can be used as an argument to the I/O functions (see - <seealso marker="stdlib:io"><c>stdlib:io(3)</c></seealso>).</p> + <seealso marker="stdlib:io"><c>io(3)</c></seealso>).</p> <note> <p>In previous versions of <c>file</c>, modes were specified as one of the atoms <c>read</c>, <c>write</c>, or @@ -1055,7 +1055,7 @@ f.txt: {person, "kalle", 25}. </taglist> <p>The encoding of <c><anno>Filename</anno></c> can be set by a comment as described in - <seealso marker="stdlib:epp#encoding"><c>stdlib:epp(3)</c></seealso>.</p> + <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p> </desc> </func> <func> @@ -1128,7 +1128,7 @@ f.txt: {person, "kalle", 25}. </taglist> <p>The encoding of <c><anno>Filename</anno></c> can be set by a comment as described in - <seealso marker="stdlib:epp#encoding"><c>stdlib:epp(3)</c></seealso>.</p> + <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p> </desc> </func> <func> @@ -1389,7 +1389,7 @@ f.txt: {person, "kalle", 25}. <c>{ok, <anno>FileInfo</anno>}</c> if successful, otherwise <c>{error, <anno>Reason</anno>}</c>. <c><anno>FileInfo</anno></c> is a record - <c>file_info</c>, defined in the <c>Kernel</c> include file + <c>file_info</c>, defined in the Kernel include file <c>file.hrl</c>. Include the following directive in the module from which the function is called:</p> <code type="none"> @@ -1552,7 +1552,7 @@ f.txt: {person, "kalle", 25}. raw line-oriented reading.</p> <p>If <c>encoding</c> is set to something else than <c>latin1</c>, the <c>read_line/1</c> call fails if the data contains characters larger than 255, - why module <seealso marker="stdlib:io"><c>stdlib:io(3)</c></seealso> is to be + why module <seealso marker="stdlib:io"><c>io(3)</c></seealso> is to be preferred when reading such a file.</p> <p>The function returns:</p> <taglist> @@ -1970,7 +1970,7 @@ f.txt: {person, "kalle", 25}. <p>Changes file information. Returns <c>ok</c> if successful, otherwise <c>{error, <anno>Reason</anno>}</c>. <c><anno>FileInfo</anno></c> is a record - <c>file_info</c>, defined in the <c>Kernel</c> include file + <c>file_info</c>, defined in the Kernel include file <c>file.hrl</c>. Include the following directive in the module from which the function is called:</p> <code type="none"> diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml index 83242c2df8..08454b9832 100644 --- a/lib/kernel/doc/src/gen_tcp.xml +++ b/lib/kernel/doc/src/gen_tcp.xml @@ -216,7 +216,7 @@ do_recv(Sock, Bs) -> time-out in milliseconds. Defaults to <c>infinity</c>.</p> <note> <p>The default values for options specified to <c>connect</c> can - be affected by the <c>Kernel</c> configuration parameter + be affected by the Kernel configuration parameter <c>inet_default_connect_options</c>. For details, see <seealso marker="inet"><c>inet(3)</c></seealso>.</p> </note> @@ -293,7 +293,7 @@ do_recv(Sock, Bs) -> <seealso marker="#accept/1"><c>accept/1,2</c></seealso>.</p> <note> <p>The default values for options specified to <c>listen</c> can - be affected by the <c>Kernel</c> configuration parameter + be affected by the Kernel configuration parameter <c>inet_default_listen_options</c>. For details, see <seealso marker="inet"><c>inet(3)</c></seealso>.</p> </note> @@ -307,7 +307,7 @@ do_recv(Sock, Bs) -> <type_desc variable="HttpPacket">See the description of <c>HttpPacket</c> in <seealso marker="erts:erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seealso> - in <c>ERTS</c>. + in ERTS. </type_desc> <desc> <p>Receives a packet from a socket in passive diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml index 864f8facac..59a046bf4d 100644 --- a/lib/kernel/doc/src/heart.xml +++ b/lib/kernel/doc/src/heart.xml @@ -83,7 +83,7 @@ <c><![CDATA[SIGKILL]]></c>:</p> <pre> % <input>erl -heart -env HEART_KILL_SIGNAL SIGABRT ...</input></pre> - <p> If heart should <b>not</b> kill the Erlang runtime system, this can be indicated + <p> If heart should <em>not</em> kill the Erlang runtime system, this can be indicated using the environment variable <c><![CDATA[HEART_NO_KILL=TRUE]]></c>. This can be useful if the command executed by heart takes care of this, for example as part of a specific cleanup sequence. diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index c0dce2f50c..3f4e3684f4 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -36,7 +36,7 @@ <seealso marker="erts:inet_cfg">ERTS User's Guide: Inet Configuration</seealso> for more information about how to configure an Erlang runtime system for IP communication.</p> - <p>The following two <c>Kernel</c> configuration parameters affect the + <p>The following two Kernel configuration parameters affect the behavior of all sockets opened on an Erlang node:</p> <list type="bulleted"> <item><p><c>inet_default_connect_options</c> can contain a list of @@ -48,7 +48,7 @@ <p>When <c>accept</c> is issued, the values of the listening socket options are inherited. No such application variable is therefore needed for <c>accept</c>.</p> - <p>Using the <c>Kernel</c> configuration parameters above, one + <p>Using the Kernel configuration parameters above, one can set default options for all TCP sockets on a node, but use this with care. Options such as <c>{delay_send,true}</c> can be specified in this way. The following is an example of starting an Erlang @@ -95,7 +95,7 @@ fe80::204:acff:fe17:bf38 <datatype> <name name="hostent"/> <desc> - <p>The record is defined in the <c>Kernel</c> include file + <p>The record is defined in the Kernel include file <c>"inet.hrl"</c>.</p> <p>Add the following directive to the module:</p> <code> @@ -151,6 +151,12 @@ fe80::204:acff:fe17:bf38 <name name="socket_address"/> </datatype> <datatype> + <name name="socket_getopt"/> + </datatype> + <datatype> + <name name="socket_setopt"/> + </datatype> + <datatype> <name name="returned_non_ip_address"/> <desc> <p> @@ -327,8 +333,6 @@ fe80::204:acff:fe17:bf38 <func> <name name="getopts" arity="2"/> <fsummary>Get one or more options for a socket.</fsummary> - <type name="socket_getopt"/> - <type name="socket_setopt"/> <desc> <p>Gets one or more options for a socket. For a list of available options, see @@ -387,7 +391,7 @@ get_tcpi_sacked(Sock) -> <<_:28/binary,TcpiSacked:32/native,_/binary>> = Info, TcpiSacked.]]></code> <p>Preferably, you would check the machine type, the operating system, - and the <c>Kernel</c> version before executing anything similar to + and the Kernel version before executing anything similar to this code.</p> </desc> </func> @@ -580,7 +584,6 @@ get_tcpi_sacked(Sock) -> <func> <name name="setopts" arity="2"/> <fsummary>Set one or more options for a socket.</fsummary> - <type name="socket_setopt"/> <desc> <p>Sets one or more options for a socket.</p> <p>The following options are available:</p> @@ -658,7 +661,7 @@ get_tcpi_sacked(Sock) -> <p>The size of the user-level software buffer used by the driver. Not to be confused with options <c>sndbuf</c> and <c>recbuf</c>, which correspond to the - <c>Kernel</c> socket buffers. It is recommended + Kernel socket buffers. It is recommended to have <c>val(buffer) >= max(val(sndbuf),val(recbuf))</c> to avoid performance issues because of unnecessary copying. <c>val(buffer)</c> is automatically set to the above @@ -717,7 +720,7 @@ get_tcpi_sacked(Sock) -> <p>The socket message queue is set to a busy state when the amount of data on the message queue reaches this limit. Notice that this limit only - concerns data that has not yet reached the <c>ERTS</c> internal + concerns data that has not yet reached the ERTS internal socket implementation. Defaults to 8 kB.</p> <p>Senders of data to the socket are suspended if either the socket message queue is busy or the socket @@ -733,7 +736,7 @@ get_tcpi_sacked(Sock) -> <tag><c>{high_watermark, Size}</c> (TCP/IP sockets)</tag> <item> <p>The socket is set to a busy state when the amount - of data queued internally by the <c>ERTS</c> socket implementation + of data queued internally by the ERTS socket implementation reaches this limit. Defaults to 8 kB.</p> <p>Senders of data to the socket are suspended if either the socket message queue is busy or the socket @@ -813,7 +816,7 @@ get_tcpi_sacked(Sock) -> socket message queue is set in a not busy state when the amount of data queued in the message queue falls below this limit. Notice that this limit only concerns data - that has not yet reached the <c>ERTS</c> internal socket + that has not yet reached the ERTS internal socket implementation. Defaults to 4 kB.</p> <p>Senders that are suspended because of either a busy message queue or a busy socket are resumed @@ -831,7 +834,7 @@ get_tcpi_sacked(Sock) -> <item> <p>If the socket is in a busy state, the socket is set in a not busy state when the amount of data - queued internally by the <c>ERTS</c> socket implementation + queued internally by the ERTS socket implementation falls below this limit. Defaults to 4 kB.</p> <p>Senders that are suspended because of a busy message queue or a busy socket are resumed @@ -951,7 +954,7 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code> are returned with the format according to <c>HttpPacket</c> described in <seealso marker="erts:erlang#decode_packet/3"> - <c>erlang:decode_packet/3</c></seealso> in <c>ERTS</c>. + <c>erlang:decode_packet/3</c></seealso> in ERTS. A socket in passive mode returns <c>{ok, HttpPacket}</c> from <c>gen_tcp:recv</c> while an active socket sends messages like @@ -1127,7 +1130,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> can respond differently to this kind of option manipulation. Use with care.</p> <p>Notice that the default options for TCP/IP sockets can be - changed with the <c>Kernel</c> configuration parameters mentioned in + changed with the Kernel configuration parameters mentioned in the beginning of this manual page.</p> </desc> </func> diff --git a/lib/kernel/doc/src/init_stub.xml b/lib/kernel/doc/src/init_stub.xml index df89b174ca..1297c8264d 100644 --- a/lib/kernel/doc/src/init_stub.xml +++ b/lib/kernel/doc/src/init_stub.xml @@ -34,6 +34,6 @@ <modulesummary>Coordination of system startup.</modulesummary> <description> <p>This module is moved to the - <seealso marker="erts:init"><c>ERTS</c></seealso> application.</p> + <seealso marker="erts:init">ERTS</seealso> application.</p> </description> </erlref> diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml index 9e6fb60bb7..df681a505f 100644 --- a/lib/kernel/doc/src/kernel_app.xml +++ b/lib/kernel/doc/src/kernel_app.xml @@ -31,12 +31,12 @@ <app>kernel</app> <appsummary>The Kernel application.</appsummary> <description> - <p>The <c>Kernel</c> application has all the code necessary to run + <p>The Kernel application has all the code necessary to run the Erlang runtime system: file servers, code servers, and so on.</p> - <p>The <c>Kernel</c> application is the first application started. It is + <p>The Kernel application is the first application started. It is mandatory in the sense that the minimal system based on - Erlang/OTP consists of <c>Kernel</c> and <c>STDLIB</c>. <c>Kernel</c> + Erlang/OTP consists of Kernel and STDLIB. Kernel contains the following functional areas:</p> <list type="bulleted"> <item>Start, stop, supervision, configuration, and distribution of applications</item> @@ -53,13 +53,13 @@ <section> <title>Error Logger Event Handlers</title> <p>Two standard error logger event handlers are defined in - the <c>Kernel</c> application. These are described in + the Kernel application. These are described in <seealso marker="error_logger"><c>error_logger(3)</c></seealso>.</p> </section> <section> <title>Configuration</title> - <p>The following configuration parameters are defined for the <c>Kernel</c> + <p>The following configuration parameters are defined for the Kernel application. For more information about configuration parameters, see file <seealso marker="app"><c>app(4)</c></seealso>.</p> <taglist> @@ -162,8 +162,8 @@ depth to which terms are printed by the error logger event handlers included in OTP. This configuration parameter is used by the two event handlers - defined by the <c>Kernel</c> application and the two event - handlers in the <c>SASL</c> application. + defined by the Kernel application and the two event + handlers in the SASL application. (If you have implemented your own error handlers, this configuration parameter has no effect on them.)</p> @@ -173,7 +173,7 @@ <c>~P</c> and <c>~W</c>, respectively, and <c>Depth</c> is used as the depth parameter. For details, see <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso> - in <c>STDLIB</c>.</p> + in STDLIB.</p> <note><p>A reasonable starting value for <c>Depth</c> is <c>30</c>. We recommend to test crashing various processes in your @@ -217,12 +217,14 @@ </item> <tag><c>{inet_dist_listen_options, Opts}</c></tag> <item> + <marker id="inet_dist_listen_options"></marker> <p>Defines a list of extra socket options to be used when opening the listening socket for a distributed Erlang node. See <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>.</p> </item> <tag><c>{inet_dist_connect_options, Opts}</c></tag> <item> + <marker id="inet_dist_connect_options"></marker> <p>Defines a list of extra socket options to be used when connecting to other distributed Erlang nodes. See <seealso marker="gen_tcp#connect/4"><c>gen_tcp:connect/4</c></seealso>.</p> @@ -239,7 +241,7 @@ <p>The name (string) of an Inet user configuration file. For details, see section <seealso marker="erts:inet_cfg"><c>Inet Configuration</c></seealso> - in the <c>ERTS</c> User's Guide.</p> + in the ERTS User's Guide.</p> </item> <tag><c>net_setuptime = SetupTime</c></tag> <item> @@ -358,7 +360,7 @@ MaxT = TickTime + TickTime / 4</code> <tag><c>start_timer = true | false</c></tag> <item> <p>Starts the <c>timer_server</c> if the parameter is - <c>true</c> (see <seealso marker="stdlib:timer"><c>stdlib:timer(3)</c></seealso>). + <c>true</c> (see <seealso marker="stdlib:timer"><c>timer(3)</c></seealso>). This parameter is to be set to <c>true</c> in an embedded system using this service.</p> <p>Defaults to <c>false</c>.</p> diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml index f48a534d4f..4e2b0c69db 100644 --- a/lib/kernel/doc/src/net_kernel.xml +++ b/lib/kernel/doc/src/net_kernel.xml @@ -55,7 +55,7 @@ $ <input>erl -sname foobar</input></pre> <seealso marker="erts:erl"><c>erl</c></seealso>.</p> <p>Normally, connections are established automatically when another node is referenced. This functionality can be disabled - by setting <c>Kernel</c> configuration parameter + by setting Kernel configuration parameter <c>dist_auto_connect</c> to <c>false</c>, see <seealso marker="kernel_app"><c>kernel(6)</c></seealso>. In this case, connections must be established explicitly by calling @@ -116,6 +116,21 @@ $ <input>erl -sname foobar</input></pre> </func> <func> + <name name="getopts" arity="2"/> + <fsummary>Get distribution socket options.</fsummary> + <desc> + <p>Get one or more options for the distribution socket + connected to <c><anno>Node</anno></c>.</p> + <p>If <c><anno>Node</anno></c> is a connected node + the return value is the same as from + <seealso marker="inet#getopts/2"><c>inet:getopts(Sock, Options)</c></seealso> + where <c>Sock</c> is the distribution socket for <c><anno>Node</anno></c>.</p> + <p>Returns <c>ignored</c> if the local node is not alive or + <c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not connected.</p> + </desc> + </func> + + <func> <name name="monitor_nodes" arity="1"/> <name name="monitor_nodes" arity="2"/> <fsummary>Subscribe to node status change messages.</fsummary> @@ -131,7 +146,7 @@ $ <input>erl -sname foobar</input></pre> are stopped. Two option lists are considered the same if they contain the same set of options.</p> - <p>As from <c>Kernel</c> version 2.11.4, and <c>ERTS</c> version + <p>As from Kernel version 2.11.4, and ERTS version 5.5.4, the following is guaranteed:</p> <list type="bulleted"> <item><p><c>nodeup</c> messages are delivered before delivery @@ -141,13 +156,13 @@ $ <input>erl -sname foobar</input></pre> messages from the remote node that have been passed through the connection have been delivered.</p></item> </list> - <p>Notice that this is <em>not</em> guaranteed for <c>Kernel</c> + <p>Notice that this is <em>not</em> guaranteed for Kernel versions before 2.11.4.</p> - <p>As from <c>Kernel</c> version 2.11.4, subscriptions can also be + <p>As from Kernel version 2.11.4, subscriptions can also be made before the <c>net_kernel</c> server is started, that is, <c>net_kernel:monitor_nodes/[1,2]</c> does not return <c>ignored</c>.</p> - <p>As from <c>Kernel</c> version 2.13, and <c>ERTS</c> version + <p>As from Kernel version 2.13, and ERTS version 5.7, the following is guaranteed:</p> <list type="bulleted"> <item><p><c>nodeup</c> messages are delivered after the @@ -157,7 +172,7 @@ $ <input>erl -sname foobar</input></pre> corresponding node has disappeared in results from <c>erlang:nodes/X</c>.</p></item> </list> - <p>Notice that this is <em>not</em> guaranteed for <c>Kernel</c> + <p>Notice that this is <em>not</em> guaranteed for Kernel versions before 2.13.</p> <p>The format of the node status change messages depends on <c><anno>Options</anno></c>. If <c><anno>Options</anno></c> is @@ -289,6 +304,27 @@ $ <input>erl -sname foobar</input></pre> </func> <func> + <name name="setopts" arity="2"/> + <fsummary>Set distribution socket options.</fsummary> + <desc> + <p>Set one or more options for distribution sockets. + Argument <c><anno>Node</anno></c> can be either one node name + or the atom <c>new</c> to affect the distribution sockets of all + future connected nodes.</p> + <p>The return value is the same as from + <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso> + or <c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not + a connected node or <c>new</c>.</p> + <p>If <c><anno>Node</anno></c> is <c>new</c> the <c><anno>Options</anno></c> + will then also be added to kernel configration parameters + <seealso marker="kernel:kernel_app#inet_dist_listen_options">inet_dist_listen_options</seealso> + and + <seealso marker="kernel:kernel_app#inet_dist_connect_options">inet_dist_connect_options</seealso>.</p> + <p>Returns <c>ignored</c> if the local node is not alive.</p> + </desc> + </func> + + <func> <name>start([Name]) -> {ok, pid()} | {error, Reason}</name> <name>start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name> <name>start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name> diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index 3d35f6f57f..9e9be3f661 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -3482,7 +3482,7 @@ types (for instance, <c>ensure_loaded/1</c> now only accepts an atom as documented; it used to accept a string too).</p> - <p><c>Dialyzer</c> will generally emit warnings for any + <p>Dialyzer will generally emit warnings for any calls that use undocumented argument types. Even if the call happens to still work in R12B, you should correct your code. A future release will adhere to the diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml index 8cad9fe4fc..5944e9321a 100644 --- a/lib/kernel/doc/src/rpc.xml +++ b/lib/kernel/doc/src/rpc.xml @@ -320,7 +320,7 @@ <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF - <seealso marker="erts:erlang#process_info/1"><c>erlang:process_info/1</c></seealso> in <c>ERTS</c>.</p> + <seealso marker="erts:erlang#process_info/1"><c>erlang:process_info/1</c></seealso> in ERTS.</p> </desc> </func> @@ -330,7 +330,7 @@ <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF - <seealso marker="erts:erlang#process_info/2"><c>erlang:process_info/2</c></seealso> in <c>ERTS</c>.</p> + <seealso marker="erts:erlang#process_info/2"><c>erlang:process_info/2</c></seealso> in ERTS.</p> </desc> </func> diff --git a/lib/kernel/doc/src/zlib_stub.xml b/lib/kernel/doc/src/zlib_stub.xml index b111581b10..9ab9c4eb62 100644 --- a/lib/kernel/doc/src/zlib_stub.xml +++ b/lib/kernel/doc/src/zlib_stub.xml @@ -34,6 +34,6 @@ <modulesummary>Zlib compression interface.</modulesummary> <description> <p>This module is moved to the - <seealso marker="erts:zlib"><c>ERTS</c></seealso> application.</p> + <seealso marker="erts:zlib">ERTS</seealso> application.</p> </description> </erlref> diff --git a/lib/kernel/include/dist_util.hrl b/lib/kernel/include/dist_util.hrl index 43e50d4325..320e916c04 100644 --- a/lib/kernel/include/dist_util.hrl +++ b/lib/kernel/include/dist_util.hrl @@ -63,7 +63,7 @@ f_getll, %% Get low level port or pid. f_address, %% The address of the "socket", %% generated from Socket,Node - %% These two are used in the tick loop, + %% These three are used in the tick loop, %% so they are not fun's to avoid holding old code. mf_tick, %% Takes the socket as parameters and %% sends a tick, this is no fun, it @@ -74,7 +74,11 @@ %% {ok, RecvCnt, SendCnt, SendPend} for %% a given socket. This is a {M,F}, %% returning {error, Reason on failure} - request_type = normal + request_type = normal, + + %% New in kernel-5.1 (OTP 19.1): + mf_setopts, %% netkernel:setopts on active connection + mf_getopts %% netkernel:getopts on active connection }). diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl index 6174136507..29307bc696 100644 --- a/lib/kernel/src/code_server.erl +++ b/lib/kernel/src/code_server.erl @@ -135,10 +135,14 @@ split_paths([], _S, Path, Paths) -> -spec call(term()) -> term(). call(Req) -> + Ref = erlang:monitor(process, ?MODULE), ?MODULE ! {code_call, self(), Req}, receive {?MODULE, Reply} -> - Reply + erlang:demonitor(Ref,[flush]), + Reply; + {'DOWN',Ref,process,_,_} -> + exit({'DOWN',code_server,Req}) end. reply(Pid, Res) -> @@ -807,7 +811,13 @@ clear_namedb([], _) -> %% Dir must be a complete pathname (not only a name). insert_dir(Dir, Db) -> Splitted = filename:split(Dir), - Name = get_name_from_splitted(Splitted), + case get_name_from_splitted(Splitted) of + Name when Name /= "ebin", Name /= "." -> + Name; + _ -> + SplittedAbsName = filename:split(absname(Dir)), + Name = get_name_from_splitted(SplittedAbsName) + end, AppDir = filename:join(del_ebin_1(Splitted)), do_insert_name(Name, AppDir, Db). @@ -933,15 +943,25 @@ del_ebin(Dir) -> filename:join(del_ebin_1(filename:split(Dir))). del_ebin_1([Parent,App,"ebin"]) -> - Ext = archive_extension(), - case filename:basename(Parent, Ext) of - Parent -> - %% Plain directory. + case filename:basename(Parent) of + [] -> + %% Parent is the root directory [Parent,App]; - Archive -> - %% Archive. - [Archive] + _ -> + Ext = archive_extension(), + case filename:basename(Parent, Ext) of + Parent -> + %% Plain directory. + [Parent,App]; + Archive -> + %% Archive. + [Archive] + end end; +del_ebin_1(Path = [_App,"ebin"]) -> + del_ebin_1(filename:split(absname(filename:join(Path)))); +del_ebin_1(["ebin"]) -> + del_ebin_1(filename:split(absname("ebin"))); del_ebin_1([H|T]) -> [H|del_ebin_1(T)]; del_ebin_1([]) -> diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl index 47d0c1b861..8d2fc4d4b7 100644 --- a/lib/kernel/src/dist_util.erl +++ b/lib/kernel/src/dist_util.erl @@ -143,7 +143,11 @@ handshake_other_started(#hs_data{request_type=ReqType}=HSData0) -> ChallengeB = recv_challenge_reply(HSData, ChallengeA, MyCookie), send_challenge_ack(HSData, gen_digest(ChallengeB, HisCookie)), ?debug({dist_util, self(), accept_connection, Node}), - connection(HSData). + connection(HSData); + +handshake_other_started(OldHsData) when element(1,OldHsData) =:= hs_data -> + handshake_other_started(convert_old_hsdata(OldHsData)). + %% %% check if connecting node is allowed to connect @@ -330,7 +334,20 @@ handshake_we_started(#hs_data{request_type=ReqType, gen_digest(ChallengeA,HisCookie)), reset_timer(NewHSData#hs_data.timer), recv_challenge_ack(NewHSData, MyChallenge, MyCookie), - connection(NewHSData). + connection(NewHSData); + +handshake_we_started(OldHsData) when element(1,OldHsData) =:= hs_data -> + handshake_we_started(convert_old_hsdata(OldHsData)). + +convert_old_hsdata({hs_data, KP, ON, TN, S, T, TF, A, OV, OF, OS, FS, FR, + FS_PRE, FS_POST, FG, FA, MFT, MFG, RT}) -> + #hs_data{ + kernel_pid = KP, other_node = ON, this_node = TN, socket = S, timer = T, + this_flags = TF, allowed = A, other_version = OV, other_flags = OF, + other_started = OS, f_send = FS, f_recv = FR, f_setopts_pre_nodeup = FS_PRE, + f_setopts_post_nodeup = FS_POST, f_getll = FG, f_address = FA, + mf_tick = MFT, mf_getstat = MFG, request_type = RT}. + %% -------------------------------------------------------------- %% The connection has been established. @@ -350,15 +367,15 @@ connection(#hs_data{other_node = Node, mark_nodeup(HSData,Address), case FPostNodeup(Socket) of ok -> - con_loop(HSData#hs_data.kernel_pid, - Node, - Socket, - Address, - HSData#hs_data.this_node, - PType, - #tick{}, - HSData#hs_data.mf_tick, - HSData#hs_data.mf_getstat); + con_loop({HSData#hs_data.kernel_pid, + Node, + Socket, + PType, + HSData#hs_data.mf_tick, + HSData#hs_data.mf_getstat, + HSData#hs_data.mf_setopts, + HSData#hs_data.mf_getopts}, + #tick{}); _ -> ?shutdown2(Node, connection_setup_failed) end; @@ -454,8 +471,8 @@ mark_nodeup(#hs_data{kernel_pid = Kernel, ?shutdown(Node) end. -con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, Type, Tick, MFTick, MFGetstat) -> +con_loop({Kernel, Node, Socket, Type, MFTick, MFGetstat, MFSetOpts, MFGetOpts}=ConData, + Tick) -> receive {tcp_closed, Socket} -> ?shutdown2(Node, connection_closed); @@ -468,15 +485,12 @@ con_loop(Kernel, Node, Socket, TcpAddress, _ -> ignore_it end, - con_loop(Kernel, Node, Socket, TcpAddress, MyNode, Type, - Tick, MFTick, MFGetstat); + con_loop(ConData, Tick); {Kernel, tick} -> case send_tick(Socket, Tick, Type, MFTick, MFGetstat) of {ok, NewTick} -> - con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, Type, NewTick, MFTick, - MFGetstat); + con_loop(ConData, NewTick); {error, not_responding} -> error_msg("** Node ~p not responding **~n" "** Removing (timedout) connection **~n", @@ -489,13 +503,24 @@ con_loop(Kernel, Node, Socket, TcpAddress, case MFGetstat(Socket) of {ok, Read, Write, _} -> From ! {self(), get_status, {ok, Read, Write}}, - con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, - Type, Tick, - MFTick, MFGetstat); + con_loop(ConData, Tick); _ -> ?shutdown2(Node, get_status_failed) - end + end; + {From, Ref, {setopts, Opts}} -> + Ret = case MFSetOpts of + undefined -> {error, enotsup}; + _ -> MFSetOpts(Socket, Opts) + end, + From ! {Ref, Ret}, + con_loop(ConData, Tick); + {From, Ref, {getopts, Opts}} -> + Ret = case MFGetOpts of + undefined -> {error, enotsup}; + _ -> MFGetOpts(Socket, Opts) + end, + From ! {Ref, Ret}, + con_loop(ConData, Tick) end. diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl index f8ef4a475d..7bc9e2ede3 100644 --- a/lib/kernel/src/erl_epmd.erl +++ b/lib/kernel/src/erl_epmd.erl @@ -103,6 +103,10 @@ names(EpmdAddr) -> register_node(Name, PortNo) -> register_node(Name, PortNo, inet). +register_node(Name, PortNo, inet_tcp) -> + register_node(Name, PortNo, inet); +register_node(Name, PortNo, inet6_tcp) -> + register_node(Name, PortNo, inet6); register_node(Name, PortNo, Family) -> gen_server:call(erl_epmd, {register, Name, PortNo, Family}, infinity). @@ -403,8 +407,6 @@ select_best_version(L1, _H1, _L2, H2) when L1 > H2 -> 0; select_best_version(_L1, H1, L2, _H2) when L2 > H1 -> 0; -select_best_version(_L1, H1, L2, _H2) when L2 > H1 -> - 0; select_best_version(_L1, H1, _L2, H2) -> erlang:min(H1, H2). diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index a91a6ed517..75dd800c6b 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -75,6 +75,7 @@ -export_type([address_family/0, hostent/0, hostname/0, ip4_address/0, ip6_address/0, ip_address/0, port_number/0, local_address/0, socket_address/0, returned_non_ip_address/0, + socket_setopt/0, socket_getopt/0, posix/0, socket/0, stat_option/0]). %% imports -import(lists, [append/1, duplicate/2, filter/2, foldl/3]). diff --git a/lib/kernel/src/inet6_tcp_dist.erl b/lib/kernel/src/inet6_tcp_dist.erl index 3aa61973af..9b6c2745d5 100644 --- a/lib/kernel/src/inet6_tcp_dist.erl +++ b/lib/kernel/src/inet6_tcp_dist.erl @@ -24,6 +24,7 @@ -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +-export([setopts/2, getopts/2]). %% ------------------------------------------------------------ %% Select this protocol based on node name @@ -72,3 +73,9 @@ close(Socket) -> is_node_name(Node) when is_atom(Node) -> inet_tcp_dist:is_node_name(Node). + +setopts(S, Opts) -> + inet_tcp_dist:setopts(S, Opts). + +getopts(S, Opts) -> + inet_tcp_dist:getopts(S, Opts). diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl index f91d7ef7c3..3084bd599a 100644 --- a/lib/kernel/src/inet_tcp_dist.erl +++ b/lib/kernel/src/inet_tcp_dist.erl @@ -24,13 +24,16 @@ -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +%% Optional +-export([setopts/2, getopts/2]). + %% Generalized dist API -export([gen_listen/2, gen_accept/2, gen_accept_connection/6, gen_setup/6, gen_select/2]). %% internal exports --export([accept_loop/3,do_accept/7,do_setup/7,getstat/1]). +-export([accept_loop/3,do_accept/7,do_setup/7,getstat/1,tick/2]). -import(error_logger,[error_msg/2]). @@ -74,7 +77,7 @@ gen_listen(Driver, Name) -> TcpAddress = get_tcp_address(Driver, Socket), {_,Port} = TcpAddress#net_address.address, ErlEpmd = net_kernel:epmd_module(), - case ErlEpmd:register_node(Name, Port) of + case ErlEpmd:register_node(Name, Port, Driver) of {ok, Creation} -> {ok, {Socket, TcpAddress, Creation}}; Error -> @@ -215,8 +218,10 @@ do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> inet:getll(S) end, f_address = fun(S, Node) -> get_remote_id(Driver, S, Node) end, - mf_tick = fun(S) -> tick(Driver, S) end, - mf_getstat = fun ?MODULE:getstat/1 + mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end, + mf_getstat = fun ?MODULE:getstat/1, + mf_setopts = fun ?MODULE:setopts/2, + mf_getopts = fun ?MODULE:getopts/2 }, dist_util:handshake_other_started(HSData); {false,IP} -> @@ -320,6 +325,7 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> {packet, 4}, nodelay()]) end, + f_getll = fun inet:getll/1, f_address = fun(_,_) -> @@ -329,9 +335,11 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> protocol = tcp, family = AddressFamily} end, - mf_tick = fun(S) -> tick(Driver, S) end, + mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end, mf_getstat = fun ?MODULE:getstat/1, - request_type = Type + request_type = Type, + mf_setopts = fun ?MODULE:setopts/2, + mf_getopts = fun ?MODULE:getopts/2 }, dist_util:handshake_we_started(HSData); _ -> @@ -492,3 +500,12 @@ split_stat([], R, W, P) -> {ok, R, W, P}. +setopts(S, Opts) -> + case [Opt || {K,_}=Opt <- Opts, + K =:= active orelse K =:= deliver orelse K =:= packet] of + [] -> inet:setopts(S,Opts); + Opts1 -> {error, {badopts,Opts1}} + end. + +getopts(S, Opts) -> + inet:getopts(S, Opts). diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index ac19f4935b..0c679e7349 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -59,6 +59,8 @@ connect_node/1, monitor_nodes/1, monitor_nodes/2, + setopts/2, + getopts/2, start/1, stop/0]). @@ -111,7 +113,7 @@ }). -record(listen, { - listen, %% listen pid + listen, %% listen socket accept, %% accepting pid address, %% #net_address module %% proto module @@ -384,7 +386,7 @@ init({Name, LongOrShortNames, TickT, CleanHalt}) -> connections = ets:new(sys_dist,[named_table, protected, - {keypos, 2}]), + {keypos, #connection.node}]), listen = Listeners, allowed = [], verbose = 0 @@ -554,6 +556,38 @@ handle_call({new_ticktime,_T,_TP}, #state{tick = #tick_change{time = T}} = State) -> async_reply({reply, {ongoing_change_to, T}, State}, From); +handle_call({setopts, new, Opts}, From, State) -> + Ret = setopts_new(Opts, State), + async_reply({reply, Ret, State}, From); + +handle_call({setopts, Node, Opts}, From, State) -> + Return = + case ets:lookup(sys_dist, Node) of + [Conn] when Conn#connection.state =:= up -> + case call_owner(Conn#connection.owner, {setopts, Opts}) of + {ok, Ret} -> Ret; + _ -> {error, noconnection} + end; + + _ -> + {error, noconnection} + end, + async_reply({reply, Return, State}, From); + +handle_call({getopts, Node, Opts}, From, State) -> + Return = + case ets:lookup(sys_dist, Node) of + [Conn] when Conn#connection.state =:= up -> + case call_owner(Conn#connection.owner, {getopts, Opts}) of + {ok, Ret} -> Ret; + _ -> {error, noconnection} + end; + + _ -> + {error, noconnection} + end, + async_reply({reply, Return, State}, From); + handle_call(_Msg, _From, State) -> {noreply, State}. @@ -1608,3 +1642,93 @@ async_gen_server_reply(From, Msg) -> {'EXIT', _} -> ok end. + +call_owner(Owner, Msg) -> + Mref = monitor(process, Owner), + Owner ! {self(), Mref, Msg}, + receive + {Mref, Reply} -> + erlang:demonitor(Mref, [flush]), + {ok, Reply}; + {'DOWN', Mref, _, _, _} -> + error + end. + + +-spec setopts(Node, Options) -> ok | {error, Reason} | ignored when + Node :: node() | new, + Options :: [inet:socket_setopt()], + Reason :: inet:posix() | noconnection. + +setopts(Node, Opts) when is_atom(Node), is_list(Opts) -> + request({setopts, Node, Opts}). + +setopts_new(Opts, State) -> + %% First try setopts on listening socket(s) + %% Bail out on failure. + %% If successful, we are pretty sure Opts are ok + %% and we continue with config params and pending connections. + case setopts_on_listen(Opts, State#state.listen) of + ok -> + setopts_new_1(Opts); + Fail -> Fail + end. + +setopts_on_listen(_, []) -> ok; +setopts_on_listen(Opts, [#listen {listen = LSocket, module = Mod} | T]) -> + try Mod:setopts(LSocket, Opts) of + ok -> + setopts_on_listen(Opts, T); + Fail -> Fail + catch + error:undef -> {error, enotsup} + end. + +setopts_new_1(Opts) -> + ConnectOpts = case application:get_env(kernel, inet_dist_connect_options) of + {ok, CO} -> CO; + _ -> [] + end, + application:set_env(kernel, inet_dist_connect_options, + merge_opts(Opts,ConnectOpts)), + ListenOpts = case application:get_env(kernel, inet_dist_listen_options) of + {ok, LO} -> LO; + _ -> [] + end, + application:set_env(kernel, inet_dist_listen_options, + merge_opts(Opts, ListenOpts)), + case lists:keyfind(nodelay, 1, Opts) of + {nodelay, ND} when is_boolean(ND) -> + application:set_env(kernel, dist_nodelay, ND); + _ -> ignore + end, + + %% Update any pending connections + PendingConns = ets:select(sys_dist, [{'_', + [{'=/=',{element,#connection.state,'$_'},up}], + ['$_']}]), + lists:foreach(fun(#connection{state = pending, owner = Owner}) -> + call_owner(Owner, {setopts, Opts}); + (#connection{state = up_pending, pending_owner = Owner}) -> + call_owner(Owner, {setopts, Opts}); + (_) -> ignore + end, PendingConns), + ok. + +merge_opts([], B) -> + B; +merge_opts([H|T], B0) -> + {Key, _} = H, + B1 = lists:filter(fun({K,_}) -> K =/= Key end, B0), + merge_opts(T, [H | B1]). + +-spec getopts(Node, Options) -> + {'ok', OptionValues} | {'error', Reason} | ignored when + Node :: node(), + Options :: [inet:socket_getopt()], + OptionValues :: [inet:socket_setopt()], + Reason :: inet:posix() | noconnection. + +getopts(Node, Opts) when is_atom(Node), is_list(Opts) -> + request({getopts, Node, Opts}). + diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index 05bbf1069e..f8519d3a5e 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -254,7 +254,19 @@ mk_cmd(_,Cmd) -> {"/bin/sh -s unix:cmd", [out], %% We insert a new line after the command, in case the command %% contains a comment character. - ["(", unicode:characters_to_binary(Cmd), "\n); echo \"\^D\"\n"], + %% + %% The </dev/null closes stdin, which means that programs + %% that use a closed stdin as an termination indicator works. + %% An example of such a program is 'more'. + %% + %% The "echo ^D" is used to indicate that the program has executed + %% and we should return any output we have gotten. We cannot use + %% termination of the child or closing of stdin/stdout as then + %% starting background jobs from os:cmd will block os:cmd. + %% + %% I tried changing this to be "better", but got bombarded with + %% backwards incompatibility bug reports, so leave this as it is. + ["(", unicode:characters_to_binary(Cmd), "\n) </dev/null; echo \"\^D\"\n"], <<$\^D>>}. validate(Atom) when is_atom(Atom) -> diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl index eb58e92224..e43be77428 100644 --- a/lib/kernel/test/erl_distribution_SUITE.erl +++ b/lib/kernel/test/erl_distribution_SUITE.erl @@ -25,6 +25,7 @@ init_per_group/2,end_per_group/2]). -export([tick/1, tick_change/1, illegal_nodenames/1, hidden_node/1, + setopts/1, table_waste/1, net_setuptime/1, inet_dist_options_options/1, @@ -42,6 +43,8 @@ -export([get_socket_priorities/0, tick_cli_test/1, tick_cli_test1/1, tick_serv_test/2, tick_serv_test1/1, + run_remote_test/1, + setopts_do/2, keep_conn/1, time_ping/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -66,6 +69,7 @@ suite() -> all() -> [tick, tick_change, illegal_nodenames, hidden_node, + setopts, table_waste, net_setuptime, inet_dist_options_options, {group, monitor_nodes}]. @@ -282,6 +286,165 @@ tick_cli_test1(Node) -> end end. +setopts(Config) when is_list(Config) -> + register(setopts_regname, self()), + [N1,N2,N3,N4] = get_nodenames(4, setopts), + + {_N1F,Port1} = start_node_unconnected(N1, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), "1", "ping"]), + 0 = wait_for_port_exit(Port1), + + {_N2F,Port2} = start_node_unconnected(N2, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), "2", "ping"]), + 0 = wait_for_port_exit(Port2), + + {ok, LSock} = gen_tcp:listen(0, [{packet,2}, {active,false}]), + {ok, LTcpPort} = inet:port(LSock), + + {N3F,Port3} = start_node_unconnected(N3, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), + "1", integer_to_list(LTcpPort)]), + wait_and_connect(LSock, N3F, Port3), + 0 = wait_for_port_exit(Port3), + + {N4F,Port4} = start_node_unconnected(N4, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), + "2", integer_to_list(LTcpPort)]), + wait_and_connect(LSock, N4F, Port4), + 0 = wait_for_port_exit(Port4), + + ok. + +wait_and_connect(LSock, NodeName, NodePort) -> + {ok, Sock} = gen_tcp:accept(LSock), + {ok, "Connect please"} = gen_tcp:recv(Sock, 0), + flush_from_port(NodePort), + pong = net_adm:ping(NodeName), + gen_tcp:send(Sock, "Connect done"), + gen_tcp:close(Sock). + + +flush_from_port(Port) -> + flush_from_port(Port, 10). + +flush_from_port(Port, Timeout) -> + receive + {Port,{data,String}} -> + io:format("~p: ~s\n", [Port, String]), + flush_from_port(Port, Timeout) + after Timeout -> + timeout + end. + +wait_for_port_exit(Port) -> + case (receive M -> M end) of + {Port,{exit_status,Status}} -> + Status; + {Port,{data,String}} -> + io:format("~p: ~s\n", [Port, String]), + wait_for_port_exit(Port) + end. + +run_remote_test([FuncStr, TestNodeStr | Args]) -> + Status = try + io:format("Node ~p started~n", [node()]), + TestNode = list_to_atom(TestNodeStr), + io:format("Node ~p spawning function ~p~n", [node(), FuncStr]), + {Pid,Ref} = spawn_monitor(?MODULE, list_to_atom(FuncStr), [TestNode, Args]), + io:format("Node ~p waiting for function ~p~n", [node(), FuncStr]), + receive + {'DOWN', Ref, process, Pid, normal} -> + 0; + Other -> + io:format("Node ~p got unexpected msg: ~p\n",[node(), Other]), + 1 + end + catch + C:E -> + io:format("Node ~p got EXCEPTION ~p:~p\nat ~p\n", + [node(), C, E, erlang:get_stacktrace()]), + 2 + end, + io:format("Node ~p doing halt(~p).\n",[node(), Status]), + erlang:halt(Status). + +% Do the actual test on the remote node +setopts_do(TestNode, [OptNr, ConnectData]) -> + [] = nodes(), + {Opt, Val} = opt_from_nr(OptNr), + ok = net_kernel:setopts(new, [{Opt, Val}]), + + [] = nodes(), + {error, noconnection} = net_kernel:getopts(TestNode, [Opt]), + + case ConnectData of + "ping" -> % We connect + net_adm:ping(TestNode); + TcpPort -> % Other connect + {ok, Sock} = gen_tcp:connect("localhost", list_to_integer(TcpPort), + [{active,false},{packet,2}]), + ok = gen_tcp:send(Sock, "Connect please"), + {ok, "Connect done"} = gen_tcp:recv(Sock, 0), + gen_tcp:close(Sock) + end, + [TestNode] = nodes(), + {ok, [{Opt,Val}]} = net_kernel:getopts(TestNode, [Opt]), + {error, noconnection} = net_kernel:getopts('pixie@fairyland', [Opt]), + + NewVal = change_val(Val), + ok = net_kernel:setopts(TestNode, [{Opt, NewVal}]), + {ok, [{Opt,NewVal}]} = net_kernel:getopts(TestNode, [Opt]), + + ok = net_kernel:setopts(TestNode, [{Opt, Val}]), + {ok, [{Opt,Val}]} = net_kernel:getopts(TestNode, [Opt]), + + ok. + +opt_from_nr("1") -> {nodelay, true}; +opt_from_nr("2") -> {nodelay, false}. + +change_val(true) -> false; +change_val(false) -> true. + +start_node_unconnected(Name, Mod, Func, Args) -> + FullName = full_node_name(Name), + CmdLine = mk_node_cmdline(Name,Mod,Func,Args), + io:format("Starting node ~p: ~s~n", [FullName, CmdLine]), + case open_port({spawn, CmdLine}, [exit_status]) of + Port when is_port(Port) -> + {FullName, Port}; + Error -> + exit({failed_to_start_node, FullName, Error}) + end. + +full_node_name(PreName) -> + HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end, + atom_to_list(node())), + list_to_atom(atom_to_list(PreName) ++ HostSuffix). + +mk_node_cmdline(Name,Mod,Func,Args) -> + Static = "-noinput", + Pa = filename:dirname(code:which(?MODULE)), + Prog = case catch init:get_argument(progname) of + {ok,[[P]]} -> P; + _ -> exit(no_progname_argument_found) + end, + NameSw = case net_kernel:longnames() of + false -> "-sname "; + true -> "-name "; + _ -> exit(not_distributed_node) + end, + {ok, Pwd} = file:get_cwd(), + NameStr = atom_to_list(Name), + Prog ++ " " + ++ Static ++ " " + ++ NameSw ++ " " ++ NameStr + ++ " -pa " ++ Pa + ++ " -env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr + ++ " -setcookie " ++ atom_to_list(erlang:get_cookie()) + ++ " -run " ++ atom_to_list(Mod) ++ " " ++ atom_to_list(Func) + ++ " " ++ string:join(Args, " "). + %% OTP-4255. tick_change(Config) when is_list(Config) -> diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index f836b2aa94..620ab235a0 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -117,7 +117,11 @@ xfer_min(Config) when is_list(Config) -> Stream = 0, Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>, Loopback = {127,0,0,1}, + StatOpts = + [recv_avg,recv_cnt,recv_max,recv_oct, + send_avg,send_cnt,send_max,send_oct], {ok,Sb} = gen_sctp:open([{type,seqpacket}]), + {ok,SbStat1} = inet:getstat(Sb, StatOpts), {ok,Pb} = inet:port(Sb), ok = gen_sctp:listen(Sb, true), @@ -212,6 +216,8 @@ xfer_min(Config) when is_list(Config) -> assoc_id=SbAssocId}} = recv_event(log_ok(gen_sctp:recv(Sb, infinity))), ok = gen_sctp:close(Sa), + {ok,SbStat2} = inet:getstat(Sb, StatOpts), + [] = filter_stat_eq(SbStat1, SbStat2), ok = gen_sctp:close(Sb), receive @@ -220,6 +226,18 @@ xfer_min(Config) when is_list(Config) -> end, ok. +filter_stat_eq([], []) -> + []; +filter_stat_eq([{Tag,Val1}=Stat|SbStat1], [{Tag,Val2}|SbStat2]) -> + if + Val1 == Val2 -> + [Stat|filter_stat_eq(SbStat1, SbStat2)]; + true -> + filter_stat_eq(SbStat1, SbStat2) + end. + + + %% Minimal data transfer in active mode. xfer_active(Config) when is_list(Config) -> Timeout = 2000, @@ -383,26 +401,28 @@ def_sndrcvinfo(Config) when is_list(Config) -> assoc_id=S2AssocId} = S2AssocChange = log_ok(gen_sctp:connect(S2, Loopback, P1, [])), ?LOGVAR(S2AssocChange), - case recv_event(log_ok(gen_sctp:recv(S1))) of - {Loopback,P2, - #sctp_assoc_change{ - state=comm_up, - error=0, - assoc_id=S1AssocId}} -> - ?LOGVAR(S1AssocId); - {Loopback,P2, - #sctp_paddr_change{ - state=addr_confirmed, - error=0, - assoc_id=S1AssocId}} -> - ?LOGVAR(S1AssocId), + S1AssocId = + case recv_event(log_ok(gen_sctp:recv(S1))) of {Loopback,P2, #sctp_assoc_change{ state=comm_up, error=0, - assoc_id=S1AssocId}} = - recv_event(log_ok(gen_sctp:recv(S1))) - end, + assoc_id=AssocId}} -> + AssocId; + {Loopback,P2, + #sctp_paddr_change{ + state=addr_confirmed, + error=0, + assoc_id=AssocId}} -> + {Loopback,P2, + #sctp_assoc_change{ + state=comm_up, + error=0, + assoc_id=AssocId}} = + recv_event(log_ok(gen_sctp:recv(S1))), + AssocId + end, + ?LOGVAR(S1AssocId), #sctp_sndrcvinfo{ ppid=17, context=0, timetolive=0} = %, assoc_id=S1AssocId} = @@ -1055,6 +1075,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> Addr = {127,0,0,1}, Stream = 0, Timeout = 333, + StartTime = timestamp(), S1 = socket_open([{ifaddr,Addr}|SockOpts], Timeout), ?LOGVAR(S1), P1 = socket_call(S1, get_port), @@ -1077,7 +1098,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> state=comm_up, assoc_id=AssocId2}}} -> AssocId2 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, ?LOGVAR(S2Ai), S1Ai = @@ -1087,7 +1108,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, ?LOGVAR(S1Ai), %% @@ -1095,13 +1116,13 @@ peeloff(Config, SockOpts) when is_list(Config) -> receive {S1,{Addr,P2,S1Ai,Stream,<<"Number one">>}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, socket_call(S2, {send,Socket1,S1Ai,Stream,<<"Number two">>}), receive {S2,{Addr,P1,S2Ai,Stream,<<"Number two">>}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% S3 = socket_peeloff(Socket1, S1Ai, SockOpts, Timeout), @@ -1120,31 +1141,31 @@ peeloff(Config, SockOpts) when is_list(Config) -> receive {S2,{Addr,P3,S2Ai,Stream,<<"Number three">>}} -> ok after Timeout -> - socket_bailout([S1,S2,S3]) + socket_bailout([S1,S2,S3], StartTime) end, socket_call(S3, {send,Socket2,S2Ai,Stream,<<"Number four">>}), receive {S3,{Addr,P2,S3Ai,Stream,<<"Number four">>}} -> ok after Timeout -> - socket_bailout([S1,S2,S3]) + socket_bailout([S1,S2,S3], StartTime) end, %% inet:i(sctp), - socket_close_verbose(S1), - socket_close_verbose(S2), + socket_close_verbose(S1, StartTime), + socket_close_verbose(S2, StartTime), receive {S3,{Addr,P2,#sctp_shutdown_event{assoc_id=S3Ai_X}}} -> match_unless_solaris(S3Ai, S3Ai_X) after Timeout -> - socket_bailout([S3]) + socket_bailout([S3], StartTime) end, receive {S3,{Addr,P2,#sctp_assoc_change{state=shutdown_comp, assoc_id=S3Ai}}} -> ok after Timeout -> - socket_bailout([S3]) + socket_bailout([S3], StartTime) end, - socket_close_verbose(S3), + socket_close_verbose(S3, StartTime), [] = flush(), ok. @@ -1156,6 +1177,7 @@ buffers(Config) when is_list(Config) -> Addr = {127,0,0,1}, Stream = 1, Timeout = 3333, + StartTime = timestamp(), S1 = socket_open([{ip,Addr}], Timeout), ?LOGVAR(S1), P1 = socket_call(S1, get_port), @@ -1174,7 +1196,7 @@ buffers(Config) when is_list(Config) -> state=comm_up, assoc_id=AssocId2}}} -> AssocId2 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, S1Ai = receive @@ -1183,7 +1205,7 @@ buffers(Config) when is_list(Config) -> state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% socket_call(S1, {setopts,[{recbuf,Limit}]}), @@ -1197,22 +1219,22 @@ buffers(Config) when is_list(Config) -> receive {S1,{Addr,P2,S1Ai,Stream,Data}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% - socket_close_verbose(S1), + socket_close_verbose(S1, StartTime), receive {S2,{Addr,P1,#sctp_shutdown_event{assoc_id=S2Ai}}} -> ok after Timeout -> - socket_bailout([S2]) + socket_bailout([S2], StartTime) end, receive {S2,{Addr,P1,#sctp_assoc_change{state=shutdown_comp, assoc_id=S2Ai}}} -> ok after Timeout -> - socket_bailout([S2]) + socket_bailout([S2], StartTime) end, - socket_close_verbose(S2), + socket_close_verbose(S2, StartTime), [] = flush(), ok. @@ -1521,8 +1543,8 @@ socket_peeloff(Socket, AssocId, SocketOpts, Timeout) -> end, s_start(Starter, Timeout). -socket_close_verbose(S) -> - History = socket_history(socket_close(S)), +socket_close_verbose(S, StartTime) -> + History = socket_history(socket_close(S), StartTime), io:format("socket_close ~p:~n ~p.~n", [S,History]), History. @@ -1535,19 +1557,19 @@ socket_call(S, Request) -> %% socket_get(S, Key) -> %% s_req(S, {get,Key}). -socket_bailout([S|Ss]) -> - History = socket_history(socket_close(S)), +socket_bailout([S|Ss], StartTime) -> + History = socket_history(socket_close(S), StartTime), io:format("bailout ~p:~n ~p.~n", [S,History]), - socket_bailout(Ss); -socket_bailout([]) -> + socket_bailout(Ss, StartTime); +socket_bailout([], _) -> io:format("flush: ~p.~n", [flush()]), ct:fail(socket_bailout). -socket_history({State,Flush}) -> +socket_history({State,Flush}, StartTime) -> {lists:keysort( 2, lists:flatten( - [[{Key,Val} || Val <- Vals] + [[{Key,{TS-StartTime,Val}} || {TS,Val} <- Vals] || {Key,Vals} <- gb_trees:to_list(State)])), Flush}. @@ -1610,14 +1632,12 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> {Parent,Ref,exit} -> ok = gen_sctp:close(Socket), Key = exit, - Val = {now(),Socket}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, Socket, State), Parent ! {self(),Ref,{NewState,flush()}}; {Parent,Ref,{Msg}} -> Result = Handler(Msg), Key = req, - Val = {now(),{Msg,Result}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Msg,Result}, State), Parent ! {self(),Ref,Result}, s_loop(Socket, Timeout, Parent, Handler, NewState); %% {Parent,Ref,{get,Key}} -> @@ -1627,16 +1647,15 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> {[#sctp_sndrcvinfo{stream=Stream,assoc_id=AssocId}=SRI],Data}} when not is_tuple(Data) -> case gb_get({assoc_change,AssocId}, State) of - [{_,{Addr,Port, - #sctp_assoc_change{ - state=comm_up, - inbound_streams=Is}}}|_] + [{Addr,Port, + #sctp_assoc_change{ + state=comm_up, + inbound_streams=Is}}|_] when 0 =< Stream, Stream < Is-> ok; [] -> ok end, Key = {msg,AssocId,Stream}, - Val = {now(),{Addr,Port,SRI,Data}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SRI,Data}, State), Parent ! {self(),{Addr,Port,AssocId,Stream,Data}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1647,13 +1666,12 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, Key = {assoc_change,AssocId}, - Val = {now(),{Addr,Port,SAC}}, case {gb_get(Key, State),St} of {[],_} -> ok; - {[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_} + {[{Addr,Port,#sctp_assoc_change{state=comm_up}}|_],_} when St =:= comm_lost; St =:= shutdown_comp -> ok end, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SAC}, State), Parent ! {self(),{Addr,Port,SAC}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1667,14 +1685,13 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, case {gb_get({assoc_change,AssocId}, State),St} of - {[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_} + {[{Addr,Port,#sctp_assoc_change{state=comm_up}}|_],_} when St =:= addr_available; St =:= addr_confirmed -> ok; {[],addr_confirmed} -> ok end, Key = {paddr_change,AssocId}, - Val = {now(),{Addr,Port,SPC}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SPC}, State), again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); {sctp,Socket,Addr,Port, @@ -1684,12 +1701,11 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, case gb_get({assoc_change,AssocId}, State) of - [{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_] -> ok; + [{Addr,Port,#sctp_assoc_change{state=comm_up}}|_] -> ok; [] -> ok end, Key = {shutdown_event,AssocId}, - Val = {now(),{Addr,Port}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port}, State), Parent ! {self(), {Addr,Port,SSE}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1707,11 +1723,12 @@ again(Socket) -> end. gb_push(Key, Val, GBT) -> + TS = timestamp(), case gb_trees:lookup(Key, GBT) of none -> - gb_trees:insert(Key, [Val], GBT); + gb_trees:insert(Key, [{TS,Val}], GBT); {value,V} -> - gb_trees:update(Key, [Val|V], GBT) + gb_trees:update(Key, [{TS,Val}|V], GBT) end. gb_get(Key, GBT) -> @@ -1719,7 +1736,7 @@ gb_get(Key, GBT) -> none -> []; {value,V} -> - V + [Val || {_TS,Val} <- V] end. match_unless_solaris(A, B) -> @@ -1727,3 +1744,6 @@ match_unless_solaris(A, B) -> {unix,sunos} -> B; _ -> A = B end. + +timestamp() -> + erlang:monotonic_time(). diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl index 1370e23195..2b59eb2bfe 100644 --- a/lib/kernel/test/init_SUITE.erl +++ b/lib/kernel/test/init_SUITE.erl @@ -27,7 +27,8 @@ -export([get_arguments/1, get_argument/1, boot_var/1, restart/1, many_restarts/0, many_restarts/1, get_plain_arguments/1, - reboot/1, stop_status/1, stop/1, get_status/1, script_id/1]). + reboot/1, stop_status/1, stop/1, get_status/1, script_id/1, + find_system_processes/0]). -export([boot1/1, boot2/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -355,12 +356,16 @@ wait_for(N,Node,EHPid) -> restart(Config) when is_list(Config) -> Args = args(), + Pa = " -pa " ++ filename:dirname(code:which(?MODULE)), + %% Currently test_server:start_node cannot be used. The restarted %% node immediately halts due to the implementation of %% test_server:start_node. - {ok, Node} = loose_node:start(init_test, Args, ?DEFAULT_TIMEOUT_SEC), + {ok, Node} = loose_node:start(init_test, Args ++ Pa, ?DEFAULT_TIMEOUT_SEC), %% Ok, the node is up, now the real test test begins. erlang:monitor_node(Node, true), + SysProcs0 = rpc:call(Node, ?MODULE, find_system_processes, []), + [InitPid, PurgerPid, LitCollectorPid, DirtyCodePid] = SysProcs0, InitPid = rpc:call(Node, erlang, whereis, [init]), PurgerPid = rpc:call(Node, erlang, whereis, [erts_code_purger]), Procs = rpc:call(Node, erlang, processes, []), @@ -375,6 +380,9 @@ restart(Config) when is_list(Config) -> end, ok = wait_restart(30, Node), + SysProcs1 = rpc:call(Node, ?MODULE, find_system_processes, []), + [InitPid1, PurgerPid1, LitCollectorPid1, DirtyCodePid1] = SysProcs1, + %% Still the same init process! InitPid1 = rpc:call(Node, erlang, whereis, [init]), InitP = pid_to_list(InitPid), @@ -385,8 +393,24 @@ restart(Config) when is_list(Config) -> PurgerP = pid_to_list(PurgerPid), PurgerP = pid_to_list(PurgerPid1), + %% and same literal area collector process! + case LitCollectorPid of + undefined -> undefined = LitCollectorPid1; + _ -> + LitCollectorP = pid_to_list(LitCollectorPid), + LitCollectorP = pid_to_list(LitCollectorPid1) + end, + + %% and same dirty process code checker process! + case DirtyCodePid of + undefined -> undefined = DirtyCodePid1; + _ -> + DirtyCodeP = pid_to_list(DirtyCodePid), + DirtyCodeP = pid_to_list(DirtyCodePid1) + end, + NewProcs0 = rpc:call(Node, erlang, processes, []), - NewProcs = NewProcs0 -- [InitPid1, PurgerPid1], + NewProcs = NewProcs0 -- SysProcs1, case check_processes(NewProcs, MaxPid) of true -> ok; @@ -406,6 +430,37 @@ restart(Config) when is_list(Config) -> loose_node:stop(Node), ok. +-record(sys_procs, {init, + code_purger, + literal_collector, + dirty_proc_checker}). + +find_system_processes() -> + find_system_procs(processes(), #sys_procs{}). + +find_system_procs([], SysProcs) -> + [SysProcs#sys_procs.init, + SysProcs#sys_procs.code_purger, + SysProcs#sys_procs.literal_collector, + SysProcs#sys_procs.dirty_proc_checker]; +find_system_procs([P|Ps], SysProcs) -> + case process_info(P, initial_call) of + {initial_call,{otp_ring0,start,2}} -> + undefined = SysProcs#sys_procs.init, + find_system_procs(Ps, SysProcs#sys_procs{init = P}); + {initial_call,{erts_code_purger,start,0}} -> + undefined = SysProcs#sys_procs.code_purger, + find_system_procs(Ps, SysProcs#sys_procs{code_purger = P}); + {initial_call,{erts_literal_area_collector,start,0}} -> + undefined = SysProcs#sys_procs.literal_collector, + find_system_procs(Ps, SysProcs#sys_procs{literal_collector = P}); + {initial_call,{erts_dirty_process_code_checker,start,0}} -> + undefined = SysProcs#sys_procs.dirty_proc_checker, + find_system_procs(Ps, SysProcs#sys_procs{dirty_proc_checker = P}); + _ -> + find_system_procs(Ps, SysProcs) + end. + wait_restart(0, _Node) -> ct:fail(not_restarted); wait_restart(N, Node) -> diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index 19ab3713a1..e76d6ec482 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -25,7 +25,7 @@ -export([space_in_cwd/1, quoting/1, cmd_unicode/1, space_in_name/1, bad_command/1, find_executable/1, unix_comment_in_command/1, deep_list_command/1, large_output_command/1, background_command/0, background_command/1, - message_leak/1, perf_counter_api/1]). + message_leak/1, close_stdin/0, close_stdin/1, perf_counter_api/1]). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +37,7 @@ all() -> [space_in_cwd, quoting, cmd_unicode, space_in_name, bad_command, find_executable, unix_comment_in_command, deep_list_command, large_output_command, background_command, message_leak, - perf_counter_api]. + close_stdin, perf_counter_api]. groups() -> []. @@ -54,7 +54,8 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(background_command, Config) -> +init_per_testcase(TC, Config) + when TC =:= background_command; TC =:= close_stdin -> case os:type() of {win32, _} -> {skip,"Should not work on windows"}; @@ -294,7 +295,7 @@ message_leak(_Config) -> case os:type() of {unix, _} -> - os:cmd("while true; do echo hello; done&"), + os:cmd("for i in $(seq 1 100); do echo hello; done&"), [] = receive_all(); _ -> ok % Cannot background on non-unix @@ -302,6 +303,16 @@ message_leak(_Config) -> process_flag(trap_exit, false). +%% Test that os:cmd closes stdin of the program that is executed +close_stdin() -> + [{timetrap, {seconds, 5}}]. +close_stdin(Config) -> + DataDir = proplists:get_value(data_dir, Config), + Fds = filename:join(DataDir, "my_fds"), + + "-1" = os:cmd(Fds). + + %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> diff --git a/lib/kernel/test/os_SUITE_data/Makefile.src b/lib/kernel/test/os_SUITE_data/Makefile.src index 912d0cbcb1..f83f781411 100644 --- a/lib/kernel/test/os_SUITE_data/Makefile.src +++ b/lib/kernel/test/os_SUITE_data/Makefile.src @@ -3,7 +3,7 @@ LD = @LD@ CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@ CROSSLDFLAGS = @CROSSLDFLAGS@ -PROGS = my_echo@exe@ +PROGS = my_echo@exe@ my_fds@exe@ all: $(PROGS) @@ -12,3 +12,9 @@ my_echo@exe@: my_echo@obj@ my_echo@obj@: my_echo.c $(CC) -c -o my_echo@obj@ $(CFLAGS) my_echo.c + +my_fds@exe@: my_fds@obj@ + $(LD) $(CROSSLDFLAGS) -o my_fds my_fds@obj@ @LIBS@ + +my_fds@obj@: my_fds.c + $(CC) -c -o my_fds@obj@ $(CFLAGS) my_fds.c diff --git a/lib/kernel/test/os_SUITE_data/my_fds.c b/lib/kernel/test/os_SUITE_data/my_fds.c new file mode 100644 index 0000000000..704a4d1e1d --- /dev/null +++ b/lib/kernel/test/os_SUITE_data/my_fds.c @@ -0,0 +1,9 @@ +#include <stdio.h> + +int +main(int argc, char** argv) +{ + char buff[1]; + int res = read(stdin, buff, 1); + printf("%d", res); +} diff --git a/lib/mnesia/doc/src/Mnesia_chap1.xml b/lib/mnesia/doc/src/Mnesia_chap1.xml index 9dfeb5efe4..035e934ed2 100644 --- a/lib/mnesia/doc/src/Mnesia_chap1.xml +++ b/lib/mnesia/doc/src/Mnesia_chap1.xml @@ -32,38 +32,38 @@ <rev>C</rev> <file>Mnesia_chap1.xml</file> </header> - <p>The <c>Mnesia</c> application provides a heavy duty real-time + <p>The Mnesia application provides a heavy duty real-time distributed database.</p> <section> <title>Scope</title> <p>This User's Guide describes how to - build <c>Mnesia</c> database applications, and how to integrate - and use the <c>Mnesia</c> database management system with + build Mnesia database applications, and how to integrate + and use the Mnesia database management system with OTP. Programming constructs are described, and numerous programming examples are included to illustrate the use of - <c>Mnesia</c>.</p> + Mnesia.</p> <p>This User's Guide is organized as follows:</p> <list type="bulleted"> <item><seealso marker="Mnesia_overview">Mnesia</seealso> provides an introduction to - <c>Mnesia</c>. + Mnesia. </item> <item><seealso marker="Mnesia_chap2">Getting Started</seealso> - introduces <c>Mnesia</c> with an example database. Examples + introduces Mnesia with an example database. Examples are included how to start an Erlang session, specify a - <c>Mnesia</c> database directory, initialize a database - schema, start <c>Mnesia</c>, and create tables. Initial + Mnesia database directory, initialize a database + schema, start Mnesia, and create tables. Initial prototyping of record definitions is also discussed. </item> <item><seealso marker="Mnesia_chap3">Build a Mnesia Database</seealso> more formally describes the steps - introduced in the previous section, namely the <c>Mnesia</c> - functions that define a database schema, start <c>Mnesia</c>, + introduced in the previous section, namely the Mnesia + functions that define a database schema, start Mnesia, and create the required tables. </item> <item><seealso marker="Mnesia_chap4">Transactions and Other Access Contexts</seealso> - describes the transactions properties that make <c>Mnesia</c> into + describes the transactions properties that make Mnesia into a fault tolerant, real-time distributed database management system. This section also describes the concept of locking to ensure consistency in tables, and "dirty @@ -76,16 +76,16 @@ features include indexing, checkpoints, distribution and fault tolerance, disc-less nodes, replication manipulation, local content tables, concurrency, and object-based programming in - <c>Mnesia</c>. + Mnesia. </item> <item><seealso marker="Mnesia_chap7">Mnesia System Information</seealso> describes the files contained in the - <c>Mnesia</c> database directory, database configuration data, + Mnesia database directory, database configuration data, core and table dumps, as well as the important subject of backup, fall-back, and disaster recovery principles. </item> <item><seealso marker="Mnesia_chap8">Combine Mnesia with - SNMP</seealso> is a short section that outlines <c>Mnesia</c> + SNMP</seealso> is a short section that outlines Mnesia integrated with SNMP. </item> <item><seealso marker="Mnesia_App_A">Appendix A: Backup diff --git a/lib/mnesia/doc/src/Mnesia_chap8.xml b/lib/mnesia/doc/src/Mnesia_chap8.xml index f1a469e315..4a2eed84d7 100644 --- a/lib/mnesia/doc/src/Mnesia_chap8.xml +++ b/lib/mnesia/doc/src/Mnesia_chap8.xml @@ -51,11 +51,11 @@ </item> </list> <p>All these approaches have different advantages and - disadvantages. <c>Mnesia</c> applications can easily be opened to + disadvantages. Mnesia applications can easily be opened to the SNMP protocol. A direct 1-to-1 mapping can be established - between <c>Mnesia</c> tables and SNMP tables. This means - that a <c>Mnesia</c> table can be configured to be <em>both</em> - a <c>Mnesia</c> table and an SNMP table. A number of functions to + between Mnesia tables and SNMP tables. This means + that a Mnesia table can be configured to be <em>both</em> + a Mnesia table and an SNMP table. A number of functions to control this behavior are described in the Reference Manual.</p> </section> </chapter> diff --git a/lib/mnesia/doc/src/Mnesia_overview.xml b/lib/mnesia/doc/src/Mnesia_overview.xml index d2d597b85d..63f2309284 100644 --- a/lib/mnesia/doc/src/Mnesia_overview.xml +++ b/lib/mnesia/doc/src/Mnesia_overview.xml @@ -39,14 +39,14 @@ high level of fault tolerance that is required in many nonstop systems, combined with requirements on the DBMS to run in the same address space as the application, have led us to implement a new - DBMS, called <c>Mnesia</c>.</p> - <p><c>Mnesia</c> is implemented in, and tightly connected to Erlang. + DBMS, called Mnesia.</p> + <p>Mnesia is implemented in, and tightly connected to Erlang. It provides the functionality that is necessary for the implementation of fault tolerant telecommunications systems.</p> - <p><c>Mnesia</c> is a multiuser distributed DBMS specially made for + <p>Mnesia is a multiuser distributed DBMS specially made for industrial telecommunications applications written in Erlang, which is also the intended target language. - <c>Mnesia</c> tries to address all the data + Mnesia tries to address all the data management issues required for typical telecommunications systems. It has a number of features that are not normally found in traditional databases.</p> @@ -54,7 +54,7 @@ from the features provided by traditional DBMSs. The applications now implemented in Erlang need a mixture of a broad range of features, which generally are not satisfied by traditional DBMSs. - <c>Mnesia</c> is designed with requirements like the following in + Mnesia is designed with requirements like the following in mind:</p> <list type="ordered"> <item>Fast real-time key/value lookup @@ -71,9 +71,9 @@ <item>Complex objects </item> </list> - <p><c>Mnesia</c> is designed with the typical data management problems - of telecommunications applications in mind. This sets <c>Mnesia</c> - apart from most other DBMS. Hence <c>Mnesia</c> + <p>Mnesia is designed with the typical data management problems + of telecommunications applications in mind. This sets Mnesia + apart from most other DBMS. Hence Mnesia combines many concepts found in traditional databases such as transactions and queries with concepts found in data management systems for telecommunications applications, for example:</p> @@ -86,7 +86,7 @@ suspending it. </item> </list> - <p><c>Mnesia</c> is also interesting because of its tight coupling to + <p>Mnesia is also interesting because of its tight coupling to Erlang, thus almost turning Erlang into a database programming language. This has many benefits, the foremost is that the impedance mismatch between the data format used by the DBMS @@ -97,7 +97,7 @@ <title>Mnesia Database Management System (DBMS)</title> <section> <title>Features</title> - <p><c>Mnesia</c> contains the following features that combine to + <p>Mnesia contains the following features that combine to produce a fault-tolerant, distributed DBMS written in Erlang: </p> <list type="bulleted"> @@ -118,7 +118,7 @@ functions can be called within one transaction. </item> <item>Several transactions can run concurrently, and their execution - is fully synchronized by the DBMS. <c>Mnesia</c> ensures that no + is fully synchronized by the DBMS. Mnesia ensures that no two processes manipulate data simultaneously. </item> <item>Transactions can be assigned the property of being executed on @@ -132,29 +132,29 @@ <section> <title>Add-On Application</title> - <p>Query List Comprehension (QLC) can be used with <c>Mnesia</c> + <p>Query List Comprehension (QLC) can be used with Mnesia to produce specialized functions that enhance the operational - ability of <c>Mnesia</c>. QLC has its own documentation as part + ability of Mnesia. QLC has its own documentation as part of the OTP documentation set. The main features of QLC - when used with <c>Mnesia</c> are as follows:</p> + when used with Mnesia are as follows:</p> <list type="bulleted"> - <item>QLC can optimize the query compiler for the <c>Mnesia</c> + <item>QLC can optimize the query compiler for the Mnesia DBMS, essentially making the DBMS more efficient. </item> <item>QLC can be used as a database programming - language for <c>Mnesia</c>. It includes a notation called "list + language for Mnesia. It includes a notation called "list comprehensions" and can be used to make complex database queries over a set of tables. </item> </list> <p>For information about QLC, see the <seealso marker="stdlib:qlc">qlc</seealso> manual page - in <c>STDLIB</c>.</p> + in STDLIB.</p> </section> <section> <title>When to Use Mnesia</title> - <p>Use <c>Mnesia</c> with the following types of applications:</p> + <p>Use Mnesia with the following types of applications:</p> <list type="bulleted"> <item>Applications that need to replicate data. </item> @@ -166,7 +166,7 @@ <item>Applications that use soft real-time characteristics. </item> </list> - <p><c>Mnesia</c> is not as appropriate with the + <p>Mnesia is not as appropriate with the following types of applications:</p> <list type="bulleted"> <item>Programs that process plain text or binary data files. @@ -176,14 +176,14 @@ library module <c>dets</c>, which is a disc-based version of the module <c>ets</c>. For information about <c>dets</c>, see the <seealso marker="stdlib:dets">dets</seealso> - manual page in <c>STDLIB</c>. + manual page in STDLIB. </item> <item>Applications that need disc logging facilities. Those applications can use the module <c>disk_log</c> by preference. For information about <c>disk_log</c>, see the <seealso marker="kernel:disk_log">disk_log</seealso> - manual page in <c>Kernel</c>. + manual page in Kernel. </item> <item>Hard real-time systems. </item> diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml index b35214cde9..621b6047ee 100644 --- a/lib/mnesia/doc/src/mnesia.xml +++ b/lib/mnesia/doc/src/mnesia.xml @@ -37,7 +37,7 @@ <description> <p>The following are some of the most important and attractive - capabilities provided by <c>Mnesia</c>:</p> + capabilities provided by Mnesia:</p> <list type="bulleted"> <item>A relational/object hybrid data model that is suitable for telecommunications applications. @@ -62,15 +62,15 @@ reconfigured at runtime without stopping the system. </item> </list> - <p>This Reference Manual describes the <c>Mnesia</c> API. This - includes functions that define and manipulate <c>Mnesia</c> + <p>This Reference Manual describes the Mnesia API. This + includes functions that define and manipulate Mnesia tables.</p> <p>All functions in this Reference Manual can be used in any combination with queries using the list comprehension notation. For information about the query notation, see the <seealso marker="stdlib:qlc">qlc</seealso> - manual page in <c>STDLIB</c>.</p> - <p>Data in <c>Mnesia</c> is organized as a set of tables. Each table + manual page in STDLIB.</p> + <p>Data in Mnesia is organized as a set of tables. Each table has a name that must be an atom. Each table is made up of Erlang records. The user is responsible for the record definitions. Each table also has a set of properties. The @@ -121,14 +121,14 @@ <item> <p><c>index</c>. This is a list of attribute names, or integers, which specify the tuple positions on which - <c>Mnesia</c> is to build and maintain an extra index + Mnesia is to build and maintain an extra index table.</p> </item> <item> <p><c>local_content</c>. When an application requires tables whose contents are local to each node, <c>local_content</c> tables can be used. The table name - is known to all <c>Mnesia</c> nodes, but its content is + is known to all Mnesia nodes, but its content is unique on each node. This means that access to such a table must be done locally. Set field <c>local_content</c> to <c>true</c> to enable the <c>local_content</c> @@ -143,7 +143,7 @@ avoid inconsistencies because of network splits.</p> </item> <item> - <p><c>snmp</c>. Each (set-based) <c>Mnesia</c> table can be + <p><c>snmp</c>. Each (set-based) Mnesia table can be automatically turned into a Simple Network Management Protocol (SNMP) ordered table as well. This property specifies the types of the SNMP keys.</p> @@ -174,7 +174,7 @@ copy of each modified record during the transaction. During iteration, that is, <c>mnesia:fold[lr]/4</c>, <c>mnesia:next/2</c>, <c>mnesia:prev/2</c>, and - <c>mnesia:snmp_get_next_index/2</c>, <c>Mnesia</c> + <c>mnesia:snmp_get_next_index/2</c>, Mnesia compensates for every written or deleted record, which can reduce the performance.</p> <p>If possible, avoid writing or deleting records in the same @@ -188,7 +188,7 @@ <desc> <p>Makes the transaction silently return the tuple <c>{aborted, Reason}</c>. - Termination of a <c>Mnesia</c> transaction means that + Termination of a Mnesia transaction means that an exception is thrown to an enclosing <c>catch</c>. Thus, the expression <c>catch mnesia:abort(x)</c> does not terminate the transaction.</p> @@ -324,7 +324,7 @@ <c>mnesia:ets</c>. Argument <c>AccessMod</c> is the name of a callback module, which implements the <c>mnesia_access</c> behavior.</p> - <p><c>Mnesia</c> forwards calls to the following functions:</p> + <p>Mnesia forwards calls to the following functions:</p> <list type="bulleted"> <item>mnesia:lock/2 (read_lock_table/1, write_lock_table/1) </item> @@ -391,15 +391,15 @@ </item> </list> <p><c>ActivityId</c> is a record that represents the identity - of the enclosing <c>Mnesia</c> activity. The first field + of the enclosing Mnesia activity. The first field (obtained with <c>element(1, ActivityId)</c>) contains an atom, which can be interpreted as the activity type: <c>ets</c>, <c>async_dirty</c>, <c>sync_dirty</c>, or <c>tid</c>. <c>tid</c> means that the activity is a transaction. The structure of the rest of the identity - record is internal to <c>Mnesia</c>.</p> + record is internal to Mnesia.</p> <p><c>Opaque</c> is an opaque data structure that is internal - to <c>Mnesia</c>.</p> + to Mnesia.</p> </desc> </func> <func> @@ -458,7 +458,7 @@ mnesia:add_table_index(person, age)</code> <desc> <marker id="async_dirty"></marker> <p>Calls the <c>Fun</c> in a context that is not protected by - a transaction. The <c>Mnesia</c> function calls performed in + a transaction. The Mnesia function calls performed in the <c>Fun</c> are mapped to the corresponding dirty functions. This still involves logging, replication, and subscriptions, but there is no locking, local transaction @@ -467,7 +467,7 @@ mnesia:add_table_index(person, age)</code> for normal <c>mnesia:dirty_*</c> operations, the operations are performed semi-asynchronously. For details, see <c>mnesia:activity/4</c> and the User's Guide.</p> - <p>The <c>Mnesia</c> tables can be manipulated without + <p>The Mnesia tables can be manipulated without using transactions. This has some serious disadvantages, but is considerably faster, as the transaction manager is not involved and no locks are set. A dirty operation does, @@ -480,7 +480,7 @@ mnesia:add_table_index(person, age)</code> read records dirty than within a transaction.</p> <p>Depending on the application, it can be a good idea to use the dirty functions for certain operations. Almost all - <c>Mnesia</c> functions that can be called within + Mnesia functions that can be called within transactions have a dirty equivalent, which is much more efficient.</p> <p>However, notice that there is a risk that the database can @@ -497,7 +497,7 @@ mnesia:add_table_index(person, age)</code> <fsummary>Backs up all tables in the database.</fsummary> <desc> <marker id="backup"></marker> - <p>Activates a new checkpoint covering all <c>Mnesia</c> tables, + <p>Activates a new checkpoint covering all Mnesia tables, including the schema, with maximum degree of redundancy, and performs a backup using <c>backup_checkpoint/2/3</c>. The default value of the backup callback module <c>BackupMod</c> @@ -529,16 +529,16 @@ mnesia:add_table_index(person, age)</code> <taglist> <tag><c>extra_db_nodes</c></tag> <item> - <p><c>Value</c> is a list of nodes that <c>Mnesia</c> + <p><c>Value</c> is a list of nodes that Mnesia is to try to connect to. <c>ReturnValue</c> is those - nodes in <c>Value</c> that <c>Mnesia</c> is connected + nodes in <c>Value</c> that Mnesia is connected to.</p> <p>Notice that this function must only be used to connect to newly started RAM nodes (N.D.R.S.N.) with an empty schema. If, for example, this function is used after the network has been partitioned, it can lead to inconsistent tables.</p> - <p>Notice that <c>Mnesia</c> can be connected to other + <p>Notice that Mnesia can be connected to other nodes than those returned in <c>ReturnValue</c>.</p> </item> <tag><c>dc_dump_limit</c></tag> @@ -548,7 +548,7 @@ mnesia:add_table_index(person, age)</code> Configuration Parameters</seealso>. <c>ReturnValue</c> is the new value. Notice that this configuration parameter is not persistent. It is lost when - <c>Mnesia</c> has stopped.</p> + Mnesia has stopped.</p> </item> </taglist> </desc> @@ -562,9 +562,9 @@ mnesia:add_table_index(person, age)</code> <c>read_write</c> but it can also be set to the atom <c>read_only</c>. If <c>AccessMode</c> is set to <c>read_only</c>, updates to the table cannot be - performed. At startup, <c>Mnesia</c> always loads + performed. At startup, Mnesia always loads <c>read_only</c> tables locally regardless of when and if - <c>Mnesia</c> is terminated on other nodes.</p> + Mnesia is terminated on other nodes.</p> </desc> </func> <func> @@ -620,13 +620,13 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> <desc> <marker id="create_schema"></marker> <p>Creates a new database on disc. Various files are - created in the local <c>Mnesia</c> directory of each node. + created in the local Mnesia directory of each node. Notice that the directory must be unique for each node. Two nodes must never share the same directory. If possible, use a local disc device to improve performance.</p> <p><c>mnesia:create_schema/1</c> fails if any of the Erlang nodes given as <c>DiscNodes</c> are not alive, if - <c>Mnesia</c> is running on any of the nodes, or if any + Mnesia is running on any of the nodes, or if any of the nodes already have a schema. Use <c>mnesia:delete_schema/1</c> to get rid of old faulty schemas.</p> @@ -638,10 +638,10 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </func> <func> <name>create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name> - <fsummary>Creates a <c>Mnesia</c> table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary> + <fsummary>Creates a Mnesia table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary> <desc> <marker id="create_table"></marker> - <p>Creates a <c>Mnesia</c> table called + <p>Creates a Mnesia table called <c>Name</c> according to argument <c>TabDef</c>. This list must be a list of <c>{Item, Value}</c> tuples, where the following values are allowed:</p> @@ -652,8 +652,8 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> set to the atom <c>read_only</c>. If <c>AccessMode</c> is set to <c>read_only</c>, updates to the table cannot be performed.</p> - <p>At startup, <c>Mnesia</c> always loads <c>read_only</c> - table locally regardless of when and if <c>Mnesia</c> is + <p>At startup, Mnesia always loads <c>read_only</c> + table locally regardless of when and if Mnesia is terminated on other nodes. This argument returns the access mode of the table. The access mode can be <c>read_only</c> or <c>read_write</c>.</p> @@ -693,7 +693,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> <item> <p><c>{index, Intlist}</c>, where <c>Intlist</c> is a list of attribute names (atoms) or - record fields for which <c>Mnesia</c> is to build and + record fields for which Mnesia is to build and maintain an extra index table. The <c>qlc</c> query compiler <em>may</em> be able to optimize queries if there are indexes available.</p> @@ -735,10 +735,10 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> <c>mnesia:create_table/2</c>, the table is immediately accessible by SNMP. Therefore applications that use SNMP to manipulate and control the system can be - designed easily, since <c>Mnesia</c> provides a + designed easily, since Mnesia provides a direct mapping between the logical tables that make up an SNMP control application and the physical data that - makes up a <c>Mnesia</c> table.</p> + makes up a Mnesia table.</p> </item> <item> <p><c>{storage_properties, [{Backend, Properties}]</c> @@ -746,7 +746,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> <c>Backend</c> can currently be <c>ets</c> or <c>dets</c>. <c>Properties</c> is a list of options sent to the back end storage during table creation. <c>Properties</c> - cannot contain properties already used by <c>Mnesia</c>, + cannot contain properties already used by Mnesia, such as <c>type</c> or <c>named_table</c>.</p> <p>For example:</p> <code type="none"> @@ -776,7 +776,7 @@ mnesia:create_table(table, [{ram_copies, [node()]}, {disc_only_copies, nodes()}, mnesia:create_table(person, [{ram_copies, [N1, N2]}, {attributes, record_info(fields, person)}]).</code> - <p>If it is required that <c>Mnesia</c> must build and + <p>If it is required that Mnesia must build and maintain an extra index table on attribute <c>address</c> of all the <c>person</c> records that are inserted in the table, the following code would be issued:</p> @@ -819,8 +819,8 @@ mnesia:create_table(person, When the last replica is deleted with this function, the table disappears entirely.</p> <p>This function can also be used to delete a replica of - the table named <c>schema</c>. The <c>Mnesia</c> node is - then removed. Notice that <c>Mnesia</c> must be + the table named <c>schema</c>. The Mnesia node is + then removed. Notice that Mnesia must be stopped on the node first.</p> </desc> </func> @@ -891,9 +891,9 @@ mnesia:create_table(person, <c>mnesia:create_schema/1</c>. <c>mnesia:delete_schema/1</c> fails if any of the Erlang nodes given as <c>DiscNodes</c> are not alive, or if - <c>Mnesia</c> is running on any of the nodes.</p> + Mnesia is running on any of the nodes.</p> <p>After the database is deleted, it can still be possible - to start <c>Mnesia</c> as a disc-less node. This depends + to start Mnesia as a disc-less node. This depends on how configuration parameter <c>schema_location</c> is set.</p> <warning> @@ -1100,7 +1100,7 @@ mnesia:create_table(person, <name>dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name> <fsummary>Dirty update of a counter record.</fsummary> <desc> - <p><c>Mnesia</c> has no special counter records. However, + <p>Mnesia has no special counter records. However, records of the form <c>{Tab, Key, Integer}</c> can be used as (possibly disc-resident) counters when <c>Tab</c> is a <c>set</c>. This function updates a counter with a positive @@ -1147,7 +1147,7 @@ mnesia:create_table(person, <desc> <marker id="dump_log"></marker> <p>Performs a user-initiated dump of the local log file. - This is usually not necessary, as <c>Mnesia</c> by default + This is usually not necessary, as Mnesia by default manages this automatically. See configuration parameters <seealso marker="#dump_log_time_threshold">dump_log_time_threshold</seealso> and @@ -1172,7 +1172,7 @@ mnesia:create_table(person, <fsummary>Dumps local tables into a text file.</fsummary> <desc> <marker id="dump_to_textfile"></marker> - <p>Dumps all local tables of a <c>Mnesia</c> system into a + <p>Dumps all local tables of a Mnesia system into a text file, which can be edited (by a normal text editor) and then be reloaded with <c>mnesia:load_textfile/1</c>. Only use this function for @@ -1182,10 +1182,10 @@ mnesia:create_table(person, </func> <func> <name>error_description(Error) -> String</name> - <fsummary>Returns a string describing a particular <c>Mnesia</c> error.</fsummary> + <fsummary>Returns a string describing a particular Mnesia error.</fsummary> <desc> <marker id="error_description"></marker> - <p>All <c>Mnesia</c> transactions, including all the schema + <p>All Mnesia transactions, including all the schema update functions, either return value <c>{atomic, Val}</c> or the tuple <c>{aborted, Reason}</c>. <c>Reason</c> can be either of the atoms in the following list. The @@ -1264,8 +1264,8 @@ mnesia:create_table(person, <desc> <marker id="ets"></marker> <p>Calls the <c>Fun</c> in a raw context that is not protected by - a transaction. The <c>Mnesia</c> function call is performed in - the <c>Fun</c> and performed directly on the local <c>ets</c> + a transaction. The Mnesia function call is performed in + the <c>Fun</c> and performed directly on the local ETS tables on the assumption that the local storage type is <c>ram_copies</c> and the tables are not replicated to other nodes. Subscriptions are not triggered and checkpoints are @@ -1321,13 +1321,13 @@ mnesia:create_table(person, <fsummary>Forces a table to be loaded into the system.</fsummary> <desc> <marker id="force_load_table"></marker> - <p>The <c>Mnesia</c> algorithm for table load can lead to a + <p>The Mnesia algorithm for table load can lead to a situation where a table cannot be loaded. This situation - occurs when a node is started and <c>Mnesia</c> concludes, or + occurs when a node is started and Mnesia concludes, or suspects, that another copy of the table was active after this local copy became inactive because of a system crash.</p> <p>If this situation is not acceptable, this function can be - used to override the strategy of the <c>Mnesia</c> table + used to override the strategy of the Mnesia table load algorithm. This can lead to a situation where some transaction effects are lost with an inconsistent database as result, but for some applications high availability is more @@ -1402,9 +1402,9 @@ mnesia:create_table(person, <desc> <marker id="info"></marker> <p>Prints system information on the terminal. - This function can be used even if <c>Mnesia</c> is not + This function can be used even if Mnesia is not started. However, more information is displayed if - <c>Mnesia</c> is started.</p> + Mnesia is started.</p> </desc> </func> <func> @@ -1431,7 +1431,7 @@ mnesia:create_table(person, <p>Installs a backup as fallback. The fallback is used to restore the database at the next startup. Installation of fallbacks requires Erlang to be operational on all the - involved nodes, but it does not matter if <c>Mnesia</c> + involved nodes, but it does not matter if Mnesia is running or not. The installation of the fallback fails if the local node is not one of the disc-resident nodes in the backup.</p> @@ -1465,14 +1465,14 @@ mnesia:create_table(person, <p><c>{mnesia_dir, AlternateDir}</c>. This argument is only valid if the scope of the installation is <c>local</c>. Normally the installation - of a fallback is targeted to the <c>Mnesia</c> directory, + of a fallback is targeted to the Mnesia directory, as configured with configuration parameter <c>-mnesia dir</c>. But by explicitly supplying an <c>AlternateDir</c>, the fallback is installed there - regardless of the <c>Mnesia</c> directory configuration + regardless of the Mnesia directory configuration parameter setting. After installation of a fallback on - an alternative <c>Mnesia</c> directory, that directory - is fully prepared for use as an active <c>Mnesia</c> + an alternative Mnesia directory, that directory + is fully prepared for use as an active Mnesia directory.</p> <p>This is a dangerous feature that must be used with care. By unintentional mixing of directories, @@ -1509,7 +1509,7 @@ mnesia:create_table(person, <marker id="load_textfile"></marker> <p>Loads a series of definitions and data found in the text file (generated with <c>mnesia:dump_to_textfile/1</c>) - into <c>Mnesia</c>. This function also starts <c>Mnesia</c> + into Mnesia. This function also starts Mnesia and possibly creates a new schema. This function is intended for educational purposes only. It is recommended to use other functions to deal with real backups.</p> @@ -1558,7 +1558,7 @@ mnesia:create_table(person, </taglist> <p>Conflicting lock requests are automatically queued if there is no risk of a deadlock. Otherwise the transaction must be - terminated and executed again. <c>Mnesia</c> does this + terminated and executed again. Mnesia does this automatically as long as the upper limit of the maximum <c>retries</c> is not reached. For details, see <c>mnesia:transaction/3</c>.</p> @@ -1726,19 +1726,19 @@ mnesia:create_table(person, </func> <func> <name>report_event(Event) -> ok</name> - <fsummary>Reports a user event to the <c>Mnesia</c> event handler.</fsummary> + <fsummary>Reports a user event to the Mnesia event handler.</fsummary> <desc> <marker id="report_event"></marker> - <p>When tracing a system of <c>Mnesia</c> applications it is - useful to be able to interleave <c>Mnesia</c> own events with + <p>When tracing a system of Mnesia applications it is + useful to be able to interleave Mnesia own events with application-related events that give information about the application context.</p> <p>Whenever the application begins a - new and demanding <c>Mnesia</c> task, or if it enters a new + new and demanding Mnesia task, or if it enters a new interesting phase in its execution, it can be a good idea to use <c>mnesia:report_event/1</c>. <c>Event</c> can be any term and generates a <c>{mnesia_user, Event}</c> event - for any processes that subscribe to <c>Mnesia</c> system + for any processes that subscribe to Mnesia system events.</p> </desc> </func> @@ -1748,7 +1748,7 @@ mnesia:create_table(person, <desc> <marker id="restore"></marker> <p>With this function, tables can be restored online from a - backup without restarting <c>Mnesia</c>. + backup without restarting Mnesia. <c>Opaque</c> is forwarded to the backup module. <c>Args</c> is a list of the following tuples:</p> <list type="bulleted"> @@ -1873,7 +1873,7 @@ mnesia:create_table(person, <p>For a complete description of <c>select</c>, see the <seealso marker="erts:index">ERTS</seealso> User's Guide and the <seealso marker="stdlib:ets">ets</seealso> manual page in - <c>STDLIB</c>.</p> + STDLIB.</p> <p>For example, to find the names of all male persons older than 30 in table <c>Tab</c>:</p> <code type="none"> @@ -1920,10 +1920,10 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </func> <func> <name>set_debug_level(Level) -> OldLevel</name> - <fsummary>Changes the internal debug level of <c>Mnesia</c>.</fsummary> + <fsummary>Changes the internal debug level of Mnesia.</fsummary> <desc> <marker id="set_debug_level"></marker> - <p>Changes the internal debug level of <c>Mnesia</c>. + <p>Changes the internal debug level of Mnesia. For details, see <seealso marker="#configuration_parameters">Section Configuration Parameters</seealso>.</p> @@ -1934,7 +1934,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> <fsummary>Sets the master nodes for all tables.</fsummary> <desc> <marker id="set_master_nodes_1"></marker> - <p>For each table <c>Mnesia</c> determines its replica nodes + <p>For each table Mnesia determines its replica nodes (<c>TabNodes</c>) and starts <c>mnesia:set_master_nodes(Tab, TabMasterNodes)</c>. where <c>TabMasterNodes</c> is the intersection of @@ -1952,16 +1952,16 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> that can have caused an inconsistent database, it can use the function <c>mnesia:set_master_nodes(Tab, MasterNodes)</c> to define from which nodes each table is to be loaded. - At startup, the <c>Mnesia</c> normal table load algorithm is + At startup, the Mnesia normal table load algorithm is bypassed and the table is loaded from one of the master nodes - defined for the table, regardless of when and if <c>Mnesia</c> + defined for the table, regardless of when and if Mnesia terminated on other nodes. <c>MasterNodes</c> can only contain nodes where the table has a replica. If the <c>MasterNodes</c> list is empty, the master node recovery mechanism for the particular table is reset, and the normal load mechanism is used at the next restart.</p> <p>The master node setting is always local. It can be - changed regardless if <c>Mnesia</c> is started or not.</p> + changed regardless if Mnesia is started or not.</p> <p>The database can also become inconsistent if configuration parameter <c>max_wait_for_decision</c> is used or if <c>mnesia:force_load_table/1</c> is used.</p> @@ -1976,7 +1976,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </func> <func> <name>snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name> - <fsummary>Gets the corresponding <c>Mnesia</c> key from an SNMP index.</fsummary> + <fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary> <type> <v>Tab ::= atom()</v> <v>RowIndex ::= [integer()]</v> @@ -1984,7 +1984,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> <v>key() ::= integer() | string() | [integer()]</v> </type> <desc> - <p>Transforms an SNMP index to the corresponding <c>Mnesia</c> + <p>Transforms an SNMP index to the corresponding Mnesia key. If the SNMP table has multiple keys, the key is a tuple of the key columns.</p> </desc> @@ -2020,7 +2020,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </func> <func> <name>snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name> - <fsummary>Organizes a <c>Mnesia</c> table as an SNMP table.</fsummary> + <fsummary>Organizes a Mnesia table as an SNMP table.</fsummary> <type> <v>Tab ::= atom()</v> <v>SnmpStruct ::= [{key, type()}]</v> @@ -2029,14 +2029,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </type> <desc> <p>A direct one-to-one mapping can be established between - <c>Mnesia</c> tables and SNMP tables. Many telecommunication + Mnesia tables and SNMP tables. Many telecommunication applications are controlled and monitored by the SNMP - protocol. This connection between <c>Mnesia</c> and SNMP + protocol. This connection between Mnesia and SNMP makes it simple and convenient to achieve this mapping.</p> <p>Argument <c>SnmpStruct</c> is a list of SNMP information. Currently, the only information needed is information about the key types in the table. Multiple - keys cannot be handled in <c>Mnesia</c>, but many SNMP + keys cannot be handled in Mnesia, but many SNMP tables have multiple keys. Therefore, the following convention is used: if a table has multiple keys, these must always be stored as a tuple of the keys. Information about @@ -2069,39 +2069,39 @@ mnesia:create_table(employee, <p>When a table is SNMP ordered, modifications are more expensive than usual, O(logN). Also, more memory is used.</p> <p>Notice that only the lexicographical SNMP ordering is - implemented in <c>Mnesia</c>, not the actual SNMP monitoring.</p> + implemented in Mnesia, not the actual SNMP monitoring.</p> </desc> </func> <func> <name>start() -> ok | {error, Reason}</name> - <fsummary>Starts a local <c>Mnesia</c> system.</fsummary> + <fsummary>Starts a local Mnesia system.</fsummary> <desc> <marker id="start"></marker> - <p>The startup procedure for a set of <c>Mnesia</c> nodes is a - fairly complicated operation. A <c>Mnesia</c> system consists - of a set of nodes, with <c>Mnesia</c> started locally on all + <p>The startup procedure for a set of Mnesia nodes is a + fairly complicated operation. A Mnesia system consists + of a set of nodes, with Mnesia started locally on all participating nodes. Normally, each node has a directory where - all the <c>Mnesia</c> files are written. This directory is - referred to as the <c>Mnesia</c> directory. <c>Mnesia</c> can + all the Mnesia files are written. This directory is + referred to as the Mnesia directory. Mnesia can also be started on disc-less nodes. For more information about disc-less nodes, see <c>mnesia:create_schema/1</c> and the User's Guide.</p> - <p>The set of nodes that makes up a <c>Mnesia</c> system is kept - in a schema. <c>Mnesia</c> nodes can be added to or removed + <p>The set of nodes that makes up a Mnesia system is kept + in a schema. Mnesia nodes can be added to or removed from the schema. The initial schema is normally created on disc with the function <c>mnesia:create_schema/1</c>. On disc-less nodes, a tiny default schema is generated each time - <c>Mnesia</c> is started. During the startup procedure, - <c>Mnesia</c> exchanges schema information between the nodes + Mnesia is started. During the startup procedure, + Mnesia exchanges schema information between the nodes to verify that the table definitions are compatible.</p> <p>Each schema has a unique cookie, which can be regarded as a unique schema identifier. The cookie must be the same on all - nodes where <c>Mnesia</c> is supposed to run. For details, + nodes where Mnesia is supposed to run. For details, see the User's Guide.</p> - <p>The schema file and all other files that <c>Mnesia</c> - needs are kept in the <c>Mnesia</c> directory. The + <p>The schema file and all other files that Mnesia + needs are kept in the Mnesia directory. The command-line option <c>-mnesia dir Dir</c> can be used to - specify the location of this directory to the <c>Mnesia</c> + specify the location of this directory to the Mnesia system. If no such command-line option is found, the name of the directory defaults to <c>Mnesia.Node</c>.</p> <p><c>application:start(mnesia)</c> can also be used.</p> @@ -2109,10 +2109,10 @@ mnesia:create_table(employee, </func> <func> <name>stop() -> stopped</name> - <fsummary>Stops <c>Mnesia</c> locally.</fsummary> + <fsummary>Stops Mnesia locally.</fsummary> <desc> <marker id="stop"></marker> - <p>Stops <c>Mnesia</c> locally on the current node.</p> + <p>Stops Mnesia locally on the current node.</p> <p><c>application:stop(mnesia)</c> can also be used.</p> </desc> </func> @@ -2132,7 +2132,7 @@ mnesia:create_table(employee, <desc> <marker id="sync_dirty"></marker> <p>Calls the <c>Fun</c> in a context that is not protected by - a transaction. The <c>Mnesia</c> function calls performed in + a transaction. The Mnesia function calls performed in the <c>Fun</c> are mapped to the corresponding dirty functions. It is performed in almost the same context as <c>mnesia:async_dirty/1,2</c>. The difference is that the @@ -2167,10 +2167,10 @@ mnesia:create_table(employee, </func> <func> <name>system_info(InfoKey) -> Info | exit({aborted, Reason})</name> - <fsummary>Returns information about the <c>Mnesia</c> system.</fsummary> + <fsummary>Returns information about the Mnesia system.</fsummary> <desc> <marker id="system_info"></marker> - <p>Returns information about the <c>Mnesia</c> system, such as + <p>Returns information about the Mnesia system, such as transaction statistics, <c>db_nodes</c>, and configuration parameters. The valid keys are as follows:</p> <list type="bulleted"> @@ -2188,7 +2188,7 @@ mnesia:create_table(employee, </item> <item> <p><c>auto_repair</c>. Returns <c>true</c> or <c>false</c> - to indicate if <c>Mnesia</c> is configured to start the + to indicate if Mnesia is configured to start the auto-repair facility on corrupted disc files.</p> </item> <item> @@ -2209,20 +2209,20 @@ mnesia:create_table(employee, in the list of nodes if they explicitly have been added to the schema, for example, with <c>mnesia:add_table_copy/3</c>. The function can be - started even if <c>Mnesia</c> is not yet running.</p> + started even if Mnesia is not yet running.</p> </item> <item> <p><c>debug</c>. Returns the current debug level of - <c>Mnesia</c>.</p> + Mnesia.</p> </item> <item> - <p><c>directory</c>. Returns the name of the <c>Mnesia</c> - directory. It can be called even if <c>Mnesia</c> is + <p><c>directory</c>. Returns the name of the Mnesia + directory. It can be called even if Mnesia is not yet running.</p> </item> <item> <p><c>dump_log_load_regulation</c>. Returns a boolean that - tells if <c>Mnesia</c> is configured to regulate the + tells if Mnesia is configured to regulate the dumper process load.</p> <p>This feature is temporary and will be removed in future releases.</p> @@ -2233,9 +2233,9 @@ mnesia:create_table(employee, </item> <item> <p><c>dump_log_update_in_place</c>. Returns a boolean that - tells if <c>Mnesia</c> is configured to perform the - updates in the <c>dets</c> files directly, or if the - updates are to be performed in a copy of the <c>dets</c> + tells if Mnesia is configured to perform the + updates in the Dets files directly, or if the + updates are to be performed in a copy of the Dets files.</p> </item> <item> @@ -2253,13 +2253,13 @@ mnesia:create_table(employee, </item> <item> <p><c>held_locks</c>. Returns a list of all - locks held by the local <c>Mnesia</c> lock manager.</p> + locks held by the local Mnesia lock manager.</p> </item> <item> <p><c>is_running</c>. Returns <c>yes</c> or <c>no</c> to - indicate if <c>Mnesia</c> is running. It can + indicate if Mnesia is running. It can also return <c>starting</c> or <c>stopping</c>. Can be - called even if <c>Mnesia</c> is not yet running.</p> + called even if Mnesia is not yet running.</p> </item> <item> <p><c>local_tables</c>. Returns a list @@ -2272,7 +2272,7 @@ mnesia:create_table(employee, </item> <item> <p><c>log_version</c>. Returns the version - number of the <c>Mnesia</c> transaction log format.</p> + number of the Mnesia transaction log format.</p> </item> <item> <p><c>master_node_tables</c>. Returns a @@ -2280,25 +2280,25 @@ mnesia:create_table(employee, </item> <item> <p><c>protocol_version</c>. Returns the version number of - the <c>Mnesia</c> inter-process communication protocol.</p> + the Mnesia inter-process communication protocol.</p> </item> <item> <p><c>running_db_nodes</c>. Returns a list of nodes where - <c>Mnesia</c> currently is running. This function can be - called even if <c>Mnesia</c> is not yet running, but it + Mnesia currently is running. This function can be + called even if Mnesia is not yet running, but it then has slightly different semantics.</p> - <p>If <c>Mnesia</c> is down on the local node, the function + <p>If Mnesia is down on the local node, the function returns those other <c>db_nodes</c> and <c>extra_db_nodes</c> that for the moment are operational.</p> - <p>If <c>Mnesia</c> is started, the function returns - those nodes that <c>Mnesia</c> on the local node is fully - connected to. Only those nodes that <c>Mnesia</c> has + <p>If Mnesia is started, the function returns + those nodes that Mnesia on the local node is fully + connected to. Only those nodes that Mnesia has exchanged schema information with are included as <c>running_db_nodes</c>. After the merge of schemas, the - local <c>Mnesia</c> system is fully operable and + local Mnesia system is fully operable and applications can perform access of remote replicas. - Before the schema merge, <c>Mnesia</c> only operates + Before the schema merge, Mnesia only operates locally. Sometimes there are more nodes included in the <c>running_db_nodes</c> list than all <c>db_nodes</c> and <c>extra_db_nodes</c> together.</p> @@ -2322,17 +2322,17 @@ mnesia:create_table(employee, <item> <p><c>transaction_failures</c>. Returns a number that indicates how many transactions have - failed since <c>Mnesia</c> was started.</p> + failed since Mnesia was started.</p> </item> <item> <p><c>transaction_commits</c>. Returns a number that indicates how many transactions have - terminated successfully since <c>Mnesia</c> was started.</p> + terminated successfully since Mnesia was started.</p> </item> <item> <p><c>transaction_restarts</c>. Returns a number that indicates how many transactions have been - restarted since <c>Mnesia</c> was started.</p> + restarted since Mnesia was started.</p> </item> <item> <p><c>transaction_log_writes</c>. @@ -2342,12 +2342,12 @@ mnesia:create_table(employee, </item> <item> <p><c>use_dir</c>. Returns a boolean that indicates if - the <c>Mnesia</c> directory is used or not. Can be - started even if <c>Mnesia</c> is not yet running.</p> + the Mnesia directory is used or not. Can be + started even if Mnesia is not yet running.</p> </item> <item> <p><c>version</c>. Returns the current - version number of <c>Mnesia</c>.</p> + version number of Mnesia.</p> </item> </list> </desc> @@ -2359,13 +2359,13 @@ mnesia:create_table(employee, <marker id="table"></marker> <p>Returns a Query List Comprehension (QLC) query handle, see the <seealso marker="stdlib:qlc">qlc(3)</seealso> - manual page in <c>STDLIB</c>. The module <c>qlc</c> - implements a query language that can use <c>Mnesia</c> + manual page in STDLIB. The module <c>qlc</c> + implements a query language that can use Mnesia tables as sources of data. Calling <c>mnesia:table/1,2</c> is the means to make the <c>mnesia</c> table <c>Tab</c> usable to QLC.</p> - <p><c>Option</c> can contain <c>Mnesia</c> - options or QLC options. <c>Mnesia</c> recognizes the + <p><c>Option</c> can contain Mnesia + options or QLC options. Mnesia recognizes the following options (any other option is forwarded to QLC).</p> <list type="bulleted"> @@ -2374,7 +2374,7 @@ mnesia:create_table(employee, </item> <item><c>{n_objects,Number}</c>, where <c>n_objects</c> specifies (roughly) the number of objects returned - from <c>Mnesia</c> to QLC. Queries to remote tables + from Mnesia to QLC. Queries to remote tables can need a larger chunk to reduce network overhead. By default, <c>100</c> objects at a time are returned. </item> @@ -2413,7 +2413,7 @@ mnesia:create_table(employee, <desc> <marker id="table_info"></marker> <p>The <c>table_info/2</c> function takes two arguments. - The first is the name of a <c>Mnesia</c> table. + The first is the name of a Mnesia table. The second is one of the following keys:</p> <list type="bulleted"> <item> @@ -2465,7 +2465,7 @@ mnesia:create_table(employee, </item> <item> <p><c>load_node</c>. Returns the name of - the node that <c>Mnesia</c> loaded the table from. The + the node that Mnesia loaded the table from. The structure of the returned value is unspecified, but can be useful for debugging purposes.</p> </item> @@ -2476,7 +2476,7 @@ mnesia:create_table(employee, </item> <item> <p><c>load_reason</c>. Returns the - reason of why <c>Mnesia</c> decided to load the table. + reason of why Mnesia decided to load the table. The structure of the returned value is unspecified, but can be useful for debugging purposes.</p> </item> @@ -2620,7 +2620,7 @@ raise(Name, Amount) -> several processes running on different nodes can concurrently execute the function <c>raise/2</c> without interfering with each other.</p> - <p>Since <c>Mnesia</c> detects deadlocks, a transaction can be + <p>Since Mnesia detects deadlocks, a transaction can be restarted any number of times. This function attempts a restart as specified in <c>Retries</c>. <c>Retries</c> must be an integer greater than 0 or the atom <c>infinity</c>. @@ -2643,7 +2643,7 @@ raise(Name, Amount) -> <p><c>NewAttributeList</c> and <c>NewRecordName</c> specify the attributes and the new record type of the converted table. Table name always remains unchanged. If - <c>record_name</c> is changed, only the <c>Mnesia</c> + <c>record_name</c> is changed, only the Mnesia functions that use table identifiers work, for example, <c>mnesia:write/3</c> works, but not <c>mnesia:write/1</c>.</p> </desc> @@ -2706,7 +2706,7 @@ raise(Name, Amount) -> distributed operation that is either performed on all nodes with disc resident schema, or none. Uninstallation of fallbacks requires Erlang to be operational on all - involved nodes, but it does not matter if <c>Mnesia</c> is + involved nodes, but it does not matter if Mnesia is running or not. Which nodes that are considered as disc-resident nodes is determined from the schema information in the local fallback.</p> @@ -2792,28 +2792,28 @@ raise(Name, Amount) -> <section> <title>Configuration Parameters</title> <marker id="configuration_parameters"></marker> - <p><c>Mnesia</c> reads the following application configuration + <p>Mnesia reads the following application configuration parameters:</p> <list type="bulleted"> <item> <p><c>-mnesia access_module Module</c>. The name of the - <c>Mnesia</c> activity access callback module. Default is + Mnesia activity access callback module. Default is <c>mnesia</c>.</p> </item> <item> <p><c>-mnesia auto_repair true | false</c>. This flag - controls if <c>Mnesia</c> automatically tries to repair + controls if Mnesia automatically tries to repair files that have not been properly closed. Default is <c>true</c>.</p> </item> <item> <p><c>-mnesia backup_module Module</c>. The name of the - <c>Mnesia</c> backup callback module. Default is + Mnesia backup callback module. Default is <c>mnesia_backup</c>.</p> </item> <item> <p><c>-mnesia debug Level</c>. Controls the debug level - of <c>Mnesia</c>. The possible values are as follows:</p> + of Mnesia. The possible values are as follows:</p> <taglist> <tag><c>none</c></tag> <item> @@ -2825,7 +2825,7 @@ raise(Name, Amount) -> events generate <c>{mnesia_info, Format, Args}</c> system events. Processes can subscribe to these events with <c>mnesia:subscribe/1</c>. The events are always sent to - the <c>Mnesia</c> event handler.</p> + the Mnesia event handler.</p> </item> <tag><c>debug</c></tag> <item> @@ -2834,15 +2834,15 @@ raise(Name, Amount) -> <c>{mnesia_info, Format, Args}</c> system events. Processes can subscribe to these events with <c>mnesia:subscribe/1</c>. The events are always sent to - the <c>Mnesia</c> event handler. On this debug level, - the <c>Mnesia</c> event handler starts subscribing to + the Mnesia event handler. On this debug level, + the Mnesia event handler starts subscribing to updates in the schema table.</p> </item> <tag><c>trace</c></tag> <item> <p>Activates all events at the debug level. On this - level, the <c>Mnesia</c> event handler starts subscribing - to updates on all <c>Mnesia</c> tables. This level is + level, the Mnesia event handler starts subscribing + to updates on all Mnesia tables. This level is intended only for debugging small toy systems, as many large events can be generated.</p> </item> @@ -2856,7 +2856,7 @@ raise(Name, Amount) -> </item> <item> <p><c>-mnesia core_dir Directory</c>. The name of the - directory where <c>Mnesia</c> core files is stored, or + directory where Mnesia core files is stored, or false. Setting it implies that also RAM-only nodes generate a core file if a crash occurs.</p> </item> @@ -2870,9 +2870,9 @@ raise(Name, Amount) -> </item> <item> <p><c>-mnesia dir Directory</c>. The name of the directory - where all <c>Mnesia</c> data is stored. The directory name + where all Mnesia data is stored. The directory name must be unique for the current node. Two nodes must never - share the the same <c>Mnesia</c> directory. The results + share the the same Mnesia directory. The results are unpredictable.</p> </item> <item> @@ -2915,44 +2915,44 @@ raise(Name, Amount) -> </item> <item> <p><c>-mnesia event_module Module</c>. The name of the - <c>Mnesia</c> event handler callback module. Default is + Mnesia event handler callback module. Default is <c>mnesia_event</c>.</p> </item> <item> <p><c>-mnesia extra_db_nodes Nodes</c> specifies a list of nodes, in addition to the ones found in the schema, with - which <c>Mnesia</c> is also to establish contact. Default + which Mnesia is also to establish contact. Default is <c>[]</c> (empty list).</p> </item> <item> <p><c>-mnesia fallback_error_function {UserModule, UserFunc}</c>. Specifies a user-supplied callback function, which is - called if a fallback is installed and <c>Mnesia</c> goes - down on another node. <c>Mnesia</c> calls the function + called if a fallback is installed and Mnesia goes + down on another node. Mnesia calls the function with one argument, the name of the dying node, for example, - <c>UserModule:UserFunc(DyingNode)</c>. <c>Mnesia</c> must + <c>UserModule:UserFunc(DyingNode)</c>. Mnesia must be restarted, otherwise the database can be inconsistent. - The default behavior is to terminate <c>Mnesia</c>.</p> + The default behavior is to terminate Mnesia.</p> </item> <item> <p><c>-mnesia max_wait_for_decision Timeout</c>. Specifies - how long <c>Mnesia</c> waits for other nodes to share their + how long Mnesia waits for other nodes to share their knowledge about the outcome of an unclear transaction. By default, <c>Timeout</c> is set to the atom <c>infinity</c>. - This implies that if <c>Mnesia</c> upon startup detects + This implies that if Mnesia upon startup detects a "heavyweight transaction" whose outcome is unclear, the - local <c>Mnesia</c> waits until <c>Mnesia</c> is started + local Mnesia waits until Mnesia is started on some (in the worst case all) of the other nodes that were involved in the interrupted transaction. This is a rare - situation, but if it occurs, <c>Mnesia</c> does not guess if + situation, but if it occurs, Mnesia does not guess if the transaction on the other nodes was committed or - terminated. <c>Mnesia</c> waits until it knows the outcome + terminated. Mnesia waits until it knows the outcome and then acts accordingly.</p> <p>If <c>Timeout</c> is set to an integer value in - milliseconds, <c>Mnesia</c> forces "heavyweight transactions" + milliseconds, Mnesia forces "heavyweight transactions" to be finished, even if the outcome of the transaction for the moment is unclear. After <c>Timeout</c> milliseconds, - <c>Mnesia</c> commits or terminates the transaction and + Mnesia commits or terminates the transaction and continues with the startup. This can lead to a situation where the transaction is committed on some nodes and terminated on other nodes. If the transaction is a @@ -2976,14 +2976,14 @@ raise(Name, Amount) -> </item> <item> <p><c>-mnesia schema_location Loc</c>. Controls where - <c>Mnesia</c> looks for its schema. Parameter + Mnesia looks for its schema. Parameter <c>Loc</c> can be one of the following atoms:</p> <taglist> <tag><c>disc</c></tag> <item> <p>Mandatory disc. The schema is assumed to be located - in the <c>Mnesia</c> directory. If the schema cannot - be found, <c>Mnesia</c> refuses to start. This is the + in the Mnesia directory. If the schema cannot + be found, Mnesia refuses to start. This is the old behavior.</p> </item> <tag><c>ram</c></tag> @@ -3001,10 +3001,10 @@ raise(Name, Amount) -> <tag><c>opt_disc</c></tag> <item> <p>Optional disc. The schema can reside on disc or in - RAM. If the schema is found on disc, <c>Mnesia</c> + RAM. If the schema is found on disc, Mnesia starts as a disc-based node and the storage type of the schema table is <c>disc_copies</c>. If no schema is - found on disc, <c>Mnesia</c> starts as a disc-less node + found on disc, Mnesia starts as a disc-less node and the storage type of the schema table is <c>ram_copies</c>. Default value for the application parameter is <c>opt_disc</c>.</p> @@ -3012,7 +3012,7 @@ raise(Name, Amount) -> </taglist> </item> </list> - <p>First, the <c>SASL</c> application parameters are checked, + <p>First, the SASL application parameters are checked, then the command-line flags are checked, and finally, the default value is chosen.</p> </section> diff --git a/lib/mnesia/doc/src/mnesia_frag_hash.xml b/lib/mnesia/doc/src/mnesia_frag_hash.xml index 95f5f8aa07..51b32129b6 100644 --- a/lib/mnesia/doc/src/mnesia_frag_hash.xml +++ b/lib/mnesia/doc/src/mnesia_frag_hash.xml @@ -87,13 +87,13 @@ the new one.</p> <p><c>NewState</c> is stored as <c>hash_state</c> among the other <c>frag_properties</c>.</p> - <p>As a part of the <c>add_frag</c> procedure, <c>Mnesia</c> iterates + <p>As a part of the <c>add_frag</c> procedure, Mnesia iterates over all fragments corresponding to the <c>IterFrags</c> numbers and starts <c>key_to_frag_number(NewState,RecordKey)</c> for each record. If the new fragment differs from the old fragment, the record is moved to the new fragment.</p> <p>As the <c>add_frag</c> procedure is a part of a schema - transaction, <c>Mnesia</c> acquires write locks on the + transaction, Mnesia acquires write locks on the affected tables. That is, both the fragments corresponding to <c>IterFrags</c> and those corresponding to <c>AdditionalLockFrags</c>.</p> @@ -112,7 +112,7 @@ <desc> <p><c>NewState</c> is stored as <c>hash_state</c> among the other <c>frag_properties</c>.</p> - <p>As a part of the <c>del_frag</c> procedure, <c>Mnesia</c> iterates + <p>As a part of the <c>del_frag</c> procedure, Mnesia iterates over all fragments corresponding to the <c>IterFrags</c> numbers and starts <c>key_to_frag_number(NewState,RecordKey)</c> for each record. If the new fragment differs from the old @@ -120,7 +120,7 @@ <p>Notice that all records in the last fragment must be moved to another fragment, as the entire fragment is deleted.</p> <p>As the <c>del_frag</c> procedure is a part of a schema - transaction, <c>Mnesia</c> acquires write locks on the + transaction, Mnesia acquires write locks on the affected tables. That is, both the fragments corresponding to <c>IterFrags</c> and those corresponding to <c>AdditionalLockFrags</c>.</p> @@ -134,7 +134,7 @@ <v>Reason = term()</v> </type> <desc> - <p>Starts whenever <c>Mnesia</c> needs to determine + <p>Starts whenever Mnesia needs to determine which fragment a certain record belongs to. It is typically started at <c>read</c>, <c>write</c>, and <c>delete</c>.</p> </desc> @@ -149,7 +149,7 @@ <v>Reason = term()</v> </type> <desc> - <p>This function is called whenever <c>Mnesia</c> needs to determine + <p>This function is called whenever Mnesia needs to determine which fragments that need to be searched for a <c>MatchSpec</c>. It is typically called by <c>select</c> and <c>match_object</c>.</p> diff --git a/lib/mnesia/doc/src/mnesia_registry.xml b/lib/mnesia/doc/src/mnesia_registry.xml index cd778ae072..a76f716981 100644 --- a/lib/mnesia/doc/src/mnesia_registry.xml +++ b/lib/mnesia/doc/src/mnesia_registry.xml @@ -38,20 +38,20 @@ <modulesummary>Dump support for registries in erl_interface.</modulesummary> <description> <p>This module is usually part of the <c>erl_interface</c> - application, but is currently part of the <c>Mnesia</c> + application, but is currently part of the Mnesia application.</p> <p>This module is mainly intended for internal use within OTP, but it has two functions that are exported for public use.</p> <p>On C-nodes, <c>erl_interface</c> has support for registry tables. These tables reside in RAM on the C-node, but can also - be dumped into <c>Mnesia</c> tables. By default, the dumping + be dumped into Mnesia tables. By default, the dumping of registry tables through <c>erl_interface</c> causes a - corresponding <c>Mnesia</c> table to be created with + corresponding Mnesia table to be created with <c>mnesia_registry:create_table/1</c>, if necessary.</p> <p>Tables that are created with these functions can be - administered as all other <c>Mnesia</c> tables. They can be + administered as all other Mnesia tables. They can be included in backups, replicas can be added, and so on. - The tables are normal <c>Mnesia</c> tables owned by the user + The tables are normal Mnesia tables owned by the user of the corresponding <c>erl_interface</c> registries.</p> </description> @@ -68,7 +68,7 @@ that is, <c>{ram_copies,[node()]}</c> or <c>{disc_copies,[node()]}</c>.</p> <p>This function is used by <c>erl_interface</c> to - create the <c>Mnesia</c> table if it does not already + create the Mnesia table if it does not already exist.</p> </desc> </func> diff --git a/lib/mnesia/doc/src/part.xml b/lib/mnesia/doc/src/part.xml index 101bdb29d4..d3ffe93937 100644 --- a/lib/mnesia/doc/src/part.xml +++ b/lib/mnesia/doc/src/part.xml @@ -30,7 +30,7 @@ <file>part.sgml</file> </header> <description> - <p>The <c>Mnesia</c> application is a distributed Database Management + <p>The Mnesia application is a distributed Database Management System (DBMS), appropriate for telecommunications applications and other Erlang applications, which require continuous operation and exhibit soft real-time properties.</p> diff --git a/lib/mnesia/doc/src/ref_man.xml b/lib/mnesia/doc/src/ref_man.xml index 662f0d61d6..7fb71b9c45 100644 --- a/lib/mnesia/doc/src/ref_man.xml +++ b/lib/mnesia/doc/src/ref_man.xml @@ -33,7 +33,7 @@ <file>refman.sgml</file> </header> <description> - <p>The <c>Mnesia</c> application is a distributed Database Management + <p>The Mnesia application is a distributed Database Management System (DBMS), appropriate for telecommunications applications and other Erlang applications, which require continuous operation and exhibit soft real-time properties.</p> diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml index 94ecef24b4..42b0fa1d8a 100644 --- a/lib/observer/doc/src/ttb.xml +++ b/lib/observer/doc/src/ttb.xml @@ -485,7 +485,7 @@ ttb:p(all, call).</input></pre> <p>For a description of the <c>match_spec()</c> syntax, see section <seealso marker="erts:match_spec"><c>Match Specifications in Erlang</c></seealso> - in <c>ERTS</c>, which explains the general match specification "language". + in ERTS, which explains the general match specification "language". </p> <note> <p>The <em>system tracer</em> for sequential tracing is diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 59f6443551..968a7620aa 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -124,6 +124,8 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) end, case get_tables2(Node, Opt) of Error = {error, _} -> + Id =:= ?ID_MNESIA andalso + wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true), self() ! Error, {noreply, State}; Tables -> @@ -217,22 +219,32 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, {noreply, State#state{tabs=Tabs}} end; -handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, +handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0, timer=Timer0}) -> - Tables = get_tables(Node, Opt), + {Tables, Opt} = case Opt0#opt.type =:= mnesia andalso get_tables2(Node, Opt0) of + Ts when is_list(Ts) -> + {Ts, Opt0}; + _ -> % false or error getting mnesia tables + Opt1 = Opt0#opt{type=ets}, + {get_tables(Node, Opt1), Opt1} + end, Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), create_menus(Parent, Opt), Timer = observer_lib:start_timer(Timer0), - {noreply, State#state{node=Node, tabs=Tabs, timer=Timer}}; + {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt}}; handle_info(not_active, State = #state{timer = Timer0}) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({error, Error}, State) -> +handle_info({error, Error}, #state{opt=Opt}=State) -> handle_error(Error), - {noreply, State}; + case Opt#opt.type of + mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true); + _ -> ok + end, + {noreply, State#state{opt=Opt#opt{type=ets}}}; handle_info(_Event, State) -> {noreply, State}. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 301bb4b32f..5732c12006 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -21,7 +21,7 @@ -behaviour(wx_object). -export([start/0, stop/0]). --export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, +-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, @@ -94,6 +94,9 @@ get_tracer() -> get_active_node() -> wx_object:call(observer, get_active_node). +get_menubar() -> + wx_object:call(observer, get_menubar). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init(_Args) -> @@ -391,6 +394,9 @@ handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> handle_call(get_active_node, _From, State=#state{node=Node}) -> {reply, Node, State}; +handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) -> + {reply, MenuBar, State}; + handle_call(stop, From, State) -> stop_servers(State), {noreply, State#state{reply_to=From}}; diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in index cb2b23d534..d26daa5eda 100644 --- a/lib/odbc/configure.in +++ b/lib/odbc/configure.in @@ -147,7 +147,7 @@ AC_SUBST(THR_LIBS) odbc_lib_link_success=no AC_SUBST(TARGET_FLAGS) case $host_os in - darwin1[[0-4]].*|darwin[[0-9]].*) + darwin1[[0-5]].*|darwin[[0-9]].*) TARGET_FLAGS="-DUNIX" if test ! -d "$with_odbc" || test "$with_odbc" = "yes"; then ODBC_LIB= -L"/usr/lib" diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml index d3dd39616f..40f9339028 100644 --- a/lib/odbc/doc/src/notes.xml +++ b/lib/odbc/doc/src/notes.xml @@ -585,7 +585,7 @@ also been extended. </item><item> The <c>configure</c> scripts of <c>erl_interface</c> and <c>odbc</c> now search for thread libraries and thread library quirks the - same way as <c>erts</c> do. </item><item> The + same way as ERTS do. </item><item> The <c>configure</c> script of the <c>odbc</c> application now also looks for odbc libraries in <c>lib64</c> and <c>lib/64</c> directories when building on a 64-bit diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl index 5727c1ca50..261dfc6f20 100644 --- a/lib/odbc/test/odbc_connect_SUITE.erl +++ b/lib/odbc/test/odbc_connect_SUITE.erl @@ -89,6 +89,7 @@ init_per_suite(Config) when is_list(Config) -> [{auto_commit, off}] ++ odbc_test_lib:platform_options()) of {ok, Ref} -> odbc:disconnect(Ref), + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()} | Config]; _ -> {skip, "ODBC is not properly setup"} @@ -129,11 +130,8 @@ init_per_testcase(_TestCase, Config) -> init_per_testcase_common(Config). init_per_testcase_common(Config) -> - test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), - Dog = test_server:timetrap(?default_timeout), - Temp = lists:keydelete(connection_ref, 1, Config), - NewConfig = lists:keydelete(watchdog, 1, Temp), - [{watchdog, Dog} | NewConfig]. + ct:pal("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), + lists:keydelete(connection_ref, 1, Config). %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ @@ -153,25 +151,22 @@ end_per_testcase(_TestCase, Config) -> end_per_testcase_common(Config). end_per_testcase_common(Config) -> - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {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). + odbc:disconnect(Ref). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -commit(doc)-> - ["Test the use of explicit commit"]; -commit(suite) -> []; +commit()-> + [{doc,"Test the use of explicit commit"}]. commit(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -205,14 +200,13 @@ commit(Config) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -rollback(doc)-> - ["Test the use of explicit rollback"]; -rollback(suite) -> []; +rollback()-> + [{doc,"Test the use of explicit rollback"}]. rollback(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), @@ -245,9 +239,8 @@ rollback(Config) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -not_explicit_commit(doc) -> - ["Test what happens if you try using commit on a auto_commit connection."]; -not_explicit_commit(suite) -> []; +not_explicit_commit() -> + [{doc,"Test what happens if you try using commit on a auto_commit connection."}]. not_explicit_commit(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}] ++ @@ -256,19 +249,17 @@ not_explicit_commit(_Config) -> 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() -> + [{doc,"Tests valid data format but invalid data in the connection parameters."}]. not_exist_db(_Config) -> {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). + ct:sleep(100). %%------------------------------------------------------------------------- -no_c_executable(doc) -> - "Test what happens if the port-program can not be found"; -no_c_executable(suite) -> []; +no_c_executable() -> + [{doc,"Test what happens if the port-program can not be found"}]. no_c_executable(_Config) -> process_flag(trap_exit, true), Dir = filename:nativename(filename:join(code:priv_dir(odbc), @@ -293,9 +284,8 @@ no_c_executable(_Config) -> end. %%------------------------------------------------------------------------ -port_dies(doc) -> - "Tests what happens if the port program dies"; -port_dies(suite) -> []; +port_dies() -> + [{doc,"Tests what happens if the port program dies"}]. port_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {status, _} = process_info(Ref, status), @@ -307,7 +297,7 @@ 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(10000), + ct:sleep(10000), undefined = process_info(Ref, status); [] -> ct:fail([erlang:port_info(P, name) || P <- erlang:ports()]) @@ -315,9 +305,8 @@ port_dies(_Config) -> %%------------------------------------------------------------------------- -control_process_dies(doc) -> - "Tests what happens if the Erlang control process dies"; -control_process_dies(suite) -> []; +control_process_dies() -> + [{doc,"Tests what happens if the Erlang control process dies"}]. control_process_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), process_flag(trap_exit, true), @@ -326,7 +315,7 @@ control_process_dies(_Config) -> [Port] -> {connected, Ref} = erlang:port_info(Port, connected), exit(Ref, kill), - test_server:sleep(500), + ct:sleep(500), undefined = erlang:port_info(Port, connected); %% Check for c-program still running, how? [] -> @@ -334,9 +323,8 @@ control_process_dies(_Config) -> end. %%------------------------------------------------------------------------- -client_dies_normal(doc) -> - ["Client dies with reason normal."]; -client_dies_normal(suite) -> []; +client_dies_normal() -> + [{doc,"Client dies with reason normal."}]. client_dies_normal(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_normal, [self()]), @@ -352,7 +340,7 @@ client_dies_normal(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_normal(Pid) -> @@ -366,9 +354,8 @@ client_normal(Pid) -> %%------------------------------------------------------------------------- -client_dies_timeout(doc) -> - ["Client dies with reason timeout."]; -client_dies_timeout(suite) -> []; +client_dies_timeout() -> + [{doc,"Client dies with reason timeout."}]. client_dies_timeout(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_timeout, [self()]), @@ -384,7 +371,7 @@ client_dies_timeout(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_timeout(Pid) -> @@ -398,9 +385,8 @@ client_timeout(Pid) -> %%------------------------------------------------------------------------- -client_dies_error(doc) -> - ["Client dies with reason error."]; -client_dies_error(suite) -> []; +client_dies_error() -> + [{doc,"Client dies with reason error."}]. client_dies_error(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_error, [self()]), @@ -416,7 +402,7 @@ client_dies_error(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_error(Pid) -> @@ -430,9 +416,8 @@ client_error(Pid) -> %%------------------------------------------------------------------------- -connect_timeout(doc) -> - ["Test the timeout for the connect function."]; -connect_timeout(suite) -> []; +connect_timeout() -> + [{doc,"Test the timeout for the connect function."}]. connect_timeout(Config) when is_list(Config) -> {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(), [{timeout, 0}] ++ @@ -442,10 +427,9 @@ connect_timeout(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -connect_port_timeout(doc) -> - ["Test the timeout for the port program to connect back to the odbc " - "application within the connect function."]; -connect_port_timeout(suite) -> []; +connect_port_timeout() -> + [{"Test the timeout for the port program to connect back to the odbc " + "application within the connect function."}]. connect_port_timeout(Config) when is_list(Config) -> %% Application environment var 'port_timeout' has been set to 0 by %% init_per_testcase/2. @@ -453,15 +437,14 @@ connect_port_timeout(Config) when is_list(Config) -> odbc_test_lib:platform_options()). %%------------------------------------------------------------------------- -timeout(doc) -> - ["Test that timeouts don't cause unwanted behavior sush as receiving" - " an anwser to a previously tiemed out query."]; -timeout(suite) -> []; +timeout() -> + [{"Test that timeouts don't cause unwanted behavior sush as receiving" + " an anwser to a previously tiemed out query."}]. timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}]), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), @@ -512,7 +495,7 @@ update_table_timeout(Table, TimeOut, Pid) -> {'EXIT', timeout} -> Pid ! timout_occurred; {updated, 1} -> - test_server:fail(database_locker_failed) + ct:fail(database_locker_failed) end, receive @@ -537,15 +520,14 @@ update_table_timeout(Table, TimeOut, Pid) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -many_timeouts(doc) -> - ["Tests that many consecutive timeouts lead to that the connection " - "is shutdown."]; -many_timeouts(suite) -> []; +many_timeouts() -> + [{doc, "Tests that many consecutive timeouts lead to that the connection " + "is shutdown."}]. many_timeouts(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -592,19 +574,18 @@ loop_many_timouts(Ref, UpdateQuery, TimeOut) -> {'EXIT',timeout} -> loop_many_timouts(Ref, UpdateQuery, TimeOut); {updated, 1} -> - test_server:fail(database_locker_failed); + ct:fail(database_locker_failed); {error, connection_closed} -> ok end. %%------------------------------------------------------------------------- -timeout_reset(doc) -> - ["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() -> + [{doc, "Check that the number of consecutive timouts is reset to 0 when " + "a successful call to the database is made."}]. timeout_reset(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -688,21 +669,20 @@ loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) -> loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts - 1); {updated, 1} -> - test_server:fail(database_locker_failed); + ct:fail(database_locker_failed); {error, connection_closed} -> - test_server:fail(connection_closed_premature) + ct:fail(connection_closed_premature) end. %%------------------------------------------------------------------------- -disconnect_on_timeout(doc) -> - ["Check that disconnect after a time out works properly"]; -disconnect_on_timeout(suite) -> []; +disconnect_on_timeout() -> + [{doc,"Check that disconnect after a time out works properly"}]. disconnect_on_timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -726,7 +706,7 @@ disconnect_on_timeout(Config) when is_list(Config) -> ok -> ok = odbc:commit(Ref, commit); nok -> - test_server:fail(database_locker_failed) + ct:fail(database_locker_failed) end. update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> @@ -744,14 +724,13 @@ update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> end. %%------------------------------------------------------------------------- -connection_closed(doc) -> - ["Checks that you get an appropriate error message if you try to" - " use a connection that has been closed"]; -connection_closed(suite) -> []; +connection_closed() -> + [{doc, "Checks that you get an appropriate error message if you try to" + " use a connection that has been closed"}]. connection_closed(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -771,14 +750,13 @@ connection_closed(Config) when is_list(Config) -> {error, connection_closed} = odbc:commit(Ref, commit). %%------------------------------------------------------------------------- -disable_scrollable_cursors(doc) -> - ["Test disabling of scrollable cursors."]; -disable_scrollable_cursors(suite) -> []; +disable_scrollable_cursors() -> + [{doc,"Test disabling of scrollable cursors."}]. disable_scrollable_cursors(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{scrollable_cursors, off}]), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -792,10 +770,10 @@ disable_scrollable_cursors(Config) when is_list(Config) -> NextResult = ?RDBMS:selected_ID(1, next), - test_server:format("Expected: ~p~n", [NextResult]), + ct:pal("Expected: ~p~n", [NextResult]), Result = odbc:next(Ref), - test_server:format("Got: ~p~n", [Result]), + ct:pal("Got: ~p~n", [Result]), NextResult = Result, {error, scrollable_cursors_disabled} = odbc:first(Ref), @@ -809,15 +787,14 @@ disable_scrollable_cursors(Config) when is_list(Config) -> {selected, _ColNames,[]} = odbc:select(Ref, next, 1). %%------------------------------------------------------------------------- -return_rows_as_lists(doc)-> - ["Test the option that a row may be returned as a list instead " - "of a tuple. Too be somewhat backward compatible."]; -return_rows_as_lists(suite) -> []; +return_rows_as_lists()-> + [{doc,"Test the option that a row may be returned as a list instead " + "of a tuple. Too be somewhat backward compatible."}]. return_rows_as_lists(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{tuple_row, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -854,29 +831,28 @@ return_rows_as_lists(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -api_missuse(doc)-> - ["Test that behaviour of the control process if the api is abused"]; -api_missuse(suite) -> []; +api_missuse()-> + [{doc,"Test that behaviour of the control process if the api is abused"}]. api_missuse(Config) when is_list(Config)-> {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), + ct:sleep(10), undefined = process_info(Ref, status), {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), + ct:sleep(10), undefined = process_info(Ref2, status), {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), + ct:sleep(10), {status, _} = process_info(Ref3, status). transaction_support_str(mysql) -> @@ -886,13 +862,13 @@ transaction_support_str(_) -> %%------------------------------------------------------------------------- -extended_errors(doc)-> - ["Test the extended errors connection option: When off; the old behaviour of just an error " - "string is returned on error. When on, the error string is replaced by a 3 element tuple " - "that also exposes underlying ODBC provider error codes."]; -extended_errors(suite) -> []; +extended_errors()-> + [{doc, + "Test the extended errors connection option: When off; the old behaviour of just an error " + "string is returned on error. When on, the error string is replaced by a 3 element tuple " + "that also exposes underlying ODBC provider error codes."}]. extended_errors(Config) when is_list(Config)-> - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {updated, _} = odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))"), diff --git a/lib/odbc/test/odbc_data_type_SUITE.erl b/lib/odbc/test/odbc_data_type_SUITE.erl index c88c00725e..a3a4bc78eb 100644 --- a/lib/odbc/test/odbc_data_type_SUITE.erl +++ b/lib/odbc/test/odbc_data_type_SUITE.erl @@ -120,6 +120,7 @@ init_per_suite(Config) when is_list(Config) -> false -> case (catch odbc:start()) of ok -> + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()}| Config]; _ -> {skip, "ODBC not startable"} @@ -191,23 +192,22 @@ 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}] ++ PlatformOptions); - LCase when LCase == "utf8"; - LCase == "nchar"; - LCase == "nvarchar" -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{binary_strings, on}] ++ PlatformOptions); - _ -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), PlatformOptions) - end, + {ok, Ref} = + case atom_to_list(Case) of + "binary" ++ _ -> + odbc:connect(?RDBMS:connection_string(), + [{binary_strings, on}] ++ PlatformOptions); + LCase when LCase == "utf8"; + LCase == "nchar"; + LCase == "nvarchar" -> + odbc:connect(?RDBMS:connection_string(), + [{binary_strings, on}] ++ PlatformOptions); + _ -> + 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]. + NewConfig = lists:keydelete(connection_ref, 1, Config), + [{connection_ref, Ref} | NewConfig]. is_fixed_upper_limit(mysql) -> false; @@ -231,28 +231,23 @@ is_supported_bit(_) -> %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_TestCase, Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), ok = odbc:disconnect(Ref), %% Clean up if needed - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {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), - test_server:timetrap_cancel(Dog), - ok. + odbc:disconnect(NewRef). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -char_fixed_lower_limit(doc) -> - ["Tests fixed length char data type lower boundaries."]; -char_fixed_lower_limit(suite) -> - []; +char_fixed_lower_limit() -> + [{doc,"Tests fixed length char data type lower boundaries."}]. char_fixed_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -287,18 +282,16 @@ char_fixed_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -char_fixed_upper_limit(doc) -> - ["Tests fixed length char data type upper boundaries."]; -char_fixed_upper_limit(suite) -> - []; +char_fixed_upper_limit() -> + [{doc,"Tests fixed length char data type upper boundaries."}]. char_fixed_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Limit unknown"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Upper limit {updated, _} = % Value == 0 || -1 driver dependent! @@ -337,14 +330,12 @@ char_fixed_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -char_fixed_padding(doc) -> - ["Tests that data that is shorter than the given size is padded " - "with blanks."]; -char_fixed_padding(suite) -> - []; +char_fixed_padding() -> + [{doc, "Tests that data that is shorter than the given size is padded " + "with blanks."}]. char_fixed_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -365,13 +356,11 @@ char_fixed_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -varchar_lower_limit(doc) -> - ["Tests variable length char data type lower boundaries."]; -varchar_lower_limit(suite) -> - []; +varchar_lower_limit() -> + [{doc,"Tests variable length char data type lower boundaries."}]. varchar_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -405,13 +394,11 @@ varchar_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -varchar_upper_limit(doc) -> - ["Tests variable length char data type upper boundaries."]; -varchar_upper_limit(suite) -> - []; +varchar_upper_limit() -> + [{doc,"Tests variable length char data type upper boundaries."}]. varchar_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -455,14 +442,12 @@ varchar_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -varchar_no_padding(doc) -> - ["Tests that data that is shorter than the given max size is not padded " - "with blanks."]; -varchar_no_padding(suite) -> - []; +varchar_no_padding() -> + [{doc, "Tests that data that is shorter than the given max size is not padded " + "with blanks."}]. varchar_no_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should NOT be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -481,13 +466,11 @@ varchar_no_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -text_lower_limit(doc) -> - ["Tests 'long' char data type lower boundaries."]; -text_lower_limit(suite) -> - []; +text_lower_limit() -> + [{doc,"Tests 'long' char data type lower boundaries."}]. text_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -504,15 +487,13 @@ text_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -text_upper_limit(doc) -> - []; -text_upper_limit(suite) -> - []; +text_upper_limit() -> + [{doc,"Tests 'text' char data type upper boundaries."}]. text_upper_limit(Config) when is_list(Config) -> {skip,"Consumes too much resources" }. -%% Ref = ?config(connection_ref, Config), -%% Table = ?config(tableName, Config), +%% Ref = proplists:get_value(connection_ref, Config), +%% Table = proplists:get_value(tableName, Config), %% {updated, _} = % Value == 0 || -1 driver dependent! %% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -534,13 +515,11 @@ text_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_char_fixed_lower_limit(doc) -> - ["Tests fixed length char data type lower boundaries."]; -binary_char_fixed_lower_limit(suite) -> - []; +binary_char_fixed_lower_limit() -> + [{doc,"Tests fixed length char data type lower boundaries."}]. binary_char_fixed_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -579,18 +558,16 @@ binary_char_fixed_lower_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -binary_char_fixed_upper_limit(doc) -> - ["Tests fixed length char data type upper boundaries."]; -binary_char_fixed_upper_limit(suite) -> - []; +binary_char_fixed_upper_limit() -> + [{doc,"Tests fixed length char data type upper boundaries."}]. binary_char_fixed_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Limit unknown"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Upper limit {updated, _} = % Value == 0 || -1 driver dependent! @@ -630,14 +607,12 @@ binary_char_fixed_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_char_fixed_padding(doc) -> - ["Tests that data that is shorter than the given size is padded " - "with blanks."]; -binary_char_fixed_padding(suite) -> - []; +binary_char_fixed_padding() -> + [{doc, "Tests that data that is shorter than the given size is padded " + "with blanks."}]. binary_char_fixed_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -658,13 +633,11 @@ binary_char_fixed_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_varchar_lower_limit(doc) -> - ["Tests variable length char data type lower boundaries."]; -binary_varchar_lower_limit(suite) -> - []; +binary_varchar_lower_limit() -> + [{doc,"Tests variable length char data type lower boundaries."}]. binary_varchar_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -701,13 +674,11 @@ binary_varchar_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_varchar_upper_limit(doc) -> - ["Tests variable length char data type upper boundaries."]; -binary_varchar_upper_limit(suite) -> - []; +binary_varchar_upper_limit() -> + [{doc,"Tests variable length char data type upper boundaries."}]. binary_varchar_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -750,14 +721,12 @@ binary_varchar_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -binary_varchar_no_padding(doc) -> - ["Tests that data that is shorter than the given max size is not padded " - "with blanks."]; -binary_varchar_no_padding(suite) -> - []; +binary_varchar_no_padding() -> + [{doc,"Tests that data that is shorter than the given max size is not padded " + "with blanks."}]. binary_varchar_no_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should NOT be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -776,13 +745,11 @@ binary_varchar_no_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_text_lower_limit(doc) -> - ["Tests 'long' char data type lower boundaries."]; -binary_text_lower_limit(suite) -> - []; +binary_text_lower_limit() -> + [{doc,"Tests 'long' char data type lower boundaries."}]. binary_text_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -799,15 +766,13 @@ binary_text_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_text_upper_limit(doc) -> - []; -binary_text_upper_limit(suite) -> - []; +binary_text_upper_limit() -> + [{doc,"Tests text char data type upper boundaries."}]. binary_text_upper_limit(Config) when is_list(Config) -> {skip,"Consumes too much resources" }. -%% Ref = ?config(connection_ref, Config), -%% Table = ?config(tableName, Config), +%% Ref = proplists:get_value(connection_ref, Config), +%% Table = proplists:get_value(tableName, Config), %% {updated, _} = % Value == 0 || -1 driver dependent! %% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -830,17 +795,15 @@ binary_text_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -tiny_int_lower_limit(doc) -> - ["Tests integer of type tinyint."]; -tiny_int_lower_limit(suite) -> - []; +tiny_int_lower_limit() -> + [{doc,"Tests integer of type tinyint."}]. tiny_int_lower_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Type tiniyint not supported"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -864,17 +827,15 @@ tiny_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -tiny_int_upper_limit(doc) -> - ["Tests integer of type tinyint."]; -tiny_int_upper_limit(suite) -> - []; +tiny_int_upper_limit() -> + [{doc,"Tests integer of type tinyint."}]. tiny_int_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Type tiniyint not supported"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -898,13 +859,11 @@ tiny_int_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -small_int_lower_limit(doc) -> - ["Tests integer of type smallint."]; -small_int_lower_limit(suite) -> - []; +small_int_lower_limit() -> + [{doc,"Tests integer of type smallint."}]. small_int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -927,13 +886,11 @@ small_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -small_int_upper_limit(doc) -> - ["Tests integer of type smallint."]; -small_int_upper_limit(suite) -> - []; +small_int_upper_limit() -> + [{doc,"Tests integer of type smallint."}]. small_int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -955,13 +912,11 @@ small_int_upper_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -int_lower_limit(doc) -> - ["Tests integer of type int."]; -int_lower_limit(suite) -> - []; +int_lower_limit() -> + [{doc,"Tests integer of type int."}]. int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -983,13 +938,11 @@ int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -int_upper_limit(doc) -> - ["Tests integer of type int."]; -int_upper_limit(suite) -> - []; +int_upper_limit() -> + [{doc,"Tests integer of type int."}]. int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1011,13 +964,11 @@ int_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -big_int_lower_limit(doc) -> - ["Tests integer of type bigint"]; -big_int_lower_limit(suite) -> - []; +big_int_lower_limit() -> + [{doc,"Tests integer of type bigint"}]. big_int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1040,13 +991,11 @@ big_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -big_int_upper_limit(doc) -> - ["Tests integer of type bigint."]; -big_int_upper_limit(suite) -> - []; +big_int_upper_limit() -> + [{doc,"Tests integer of type bigint."}]. big_int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1068,17 +1017,13 @@ big_int_upper_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -bit_false(doc) -> - [""]; -bit_false(suite) -> - []; bit_false(Config) when is_list(Config) -> case ?RDBMS of oracle -> {skip, "Not supported by driver"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1102,17 +1047,13 @@ bit_false(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -bit_true(doc) -> - [""]; -bit_true(suite) -> - []; bit_true(Config) when is_list(Config) -> case ?RDBMS of oracle -> {skip, "Not supported by driver"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! @@ -1136,14 +1077,11 @@ bit_true(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_lower_limit(doc) -> - [""]; -float_lower_limit(suite) -> - []; + float_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of mysql -> @@ -1186,13 +1124,10 @@ float_lower_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_upper_limit(doc) -> - [""]; -float_upper_limit(suite) -> - []; + float_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of mysql -> @@ -1218,13 +1153,11 @@ float_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_zero(doc) -> - ["Test the float value zero."]; -float_zero(suite) -> - []; +float_zero() -> + [{doc,"Test the float value zero."}]. float_zero(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1237,13 +1170,11 @@ float_zero(Config) when is_list(Config) -> SelectResult = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table). %%------------------------------------------------------------------------- -real_zero(doc) -> - ["Test the real value zero."]; -real_zero(suite) -> - []; +real_zero() -> + [{doc,"Test the real value zero."}]. real_zero(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -1262,13 +1193,11 @@ real_zero(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table) end. %%------------------------------------------------------------------------ -dec_long(doc) -> - [""]; dec_long(suit) -> []; dec_long(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1281,13 +1210,11 @@ dec_long(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ -dec_double(doc) -> - [""]; dec_double(suit) -> []; dec_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1329,13 +1256,11 @@ dec_double(Config) when is_list(Config) -> ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ -dec_bignum(doc) -> - [""]; dec_bignum(suit) -> []; dec_bignum(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1361,13 +1286,11 @@ dec_bignum(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ -num_long(doc) -> - [""]; num_long(suit) -> []; num_long(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1380,13 +1303,11 @@ num_long(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ -num_double(doc) -> - [""]; num_double(suit) -> []; num_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1426,13 +1347,11 @@ num_double(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ -num_bignum(doc) -> - [""]; num_bignum(suit) -> []; num_bignum(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1459,13 +1378,13 @@ num_bignum(Config) when is_list(Config) -> ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ -utf8(doc) -> - ["Test unicode support"]; +utf8() -> + [{doc,"Test unicode support"}]. utf8(suit) -> []; utf8(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ "(FIELD text)"), @@ -1487,30 +1406,30 @@ utf8(Config) when is_list(Config) -> end, Latin1Data), - test_server:format("UnicodeIn: ~p ~n",[UnicodeIn]), + ct:pal("UnicodeIn: ~p ~n",[UnicodeIn]), {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)", [{{sql_varchar,50}, UnicodeIn}]), {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table), - test_server:format("UnicodeOut: ~p~n", [UnicodeOut]), + ct:pal("UnicodeOut: ~p~n", [UnicodeOut]), Result = lists:map(fun({Char}) -> unicode:characters_to_list(Char,utf8) end, UnicodeOut), - test_server:format("Result: ~p ~n", [Result]), + ct:pal("Result: ~p ~n", [Result]), Latin1Data = Result. %%------------------------------------------------------------------------ -nchar(doc) -> - ["Test unicode nchar support in sqlserver"]; +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), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1520,13 +1439,13 @@ nchar(Config) when is_list(Config) -> %%------------------------------------------------------------------------ -nvarchar(doc) -> - ["Test 'unicode' nvarchar support"]; +nvarchar() -> + [{doc,"Test 'unicode' nvarchar support"}]. nvarchar(suit) -> []; nvarchar(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1535,13 +1454,11 @@ nvarchar(Config) when is_list(Config) -> w_char_support(Ref, Table, sql_wlongvarchar, 50). %%------------------------------------------------------------------------ -timestamp(doc) -> - [""]; timestamp(suit) -> []; timestamp(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1582,21 +1499,21 @@ w_char_support(Ref, Table, CharType, Size) -> end, Latin1Data), - test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]), + ct:pal("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]), + ct:pal("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]), + ct:pal("Result: ~p~n", [PadResult]), Result = lists:map(fun(Str) -> string:strip(Str) end, PadResult), diff --git a/lib/odbc/test/odbc_query_SUITE.erl b/lib/odbc/test/odbc_query_SUITE.erl index 5f719b7287..c283872965 100644 --- a/lib/odbc/test/odbc_query_SUITE.erl +++ b/lib/odbc/test/odbc_query_SUITE.erl @@ -113,6 +113,7 @@ init_per_suite(Config) when is_list(Config) -> false -> case (catch odbc:start()) of ok -> + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()}| Config]; _ -> {skip, "ODBC not startable"} @@ -144,10 +145,10 @@ end_per_suite(_Config) -> init_per_testcase(_Case, Config) -> {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), - [{watchdog, Dog}, {connection_ref, Ref} | NewConfig]. + + NewConfig = lists:keydelete(connection_ref, 1, Config), + + [{connection_ref, Ref} | NewConfig]. %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ @@ -158,27 +159,23 @@ init_per_testcase(_Case, Config) -> %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_Case, Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), ok = odbc:disconnect(Ref), %% Clean up if needed - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {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), - test_server:timetrap_cancel(Dog), - ok. + odbc:disconnect(NewRef). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -stored_proc(doc)-> - ["Test stored proc with OUT param"]; -stored_proc(suite) -> []; +stored_proc()-> + [{doc, "Test stored proc with OUT param"}]. stored_proc(Config) when is_list(Config) -> case ?RDBMS of X when X == oracle; X == postgres-> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), {updated, _} = odbc:sql_query(Ref, ?RDBMS:stored_proc_integer_out()), @@ -192,12 +189,11 @@ stored_proc(Config) when is_list(Config) -> {skip, "stored proc not yet supported"} end. -sql_query(doc)-> - ["Test the common cases"]; -sql_query(suite) -> []; +sql_query()-> + [{doc, "Test the common cases"}]. sql_query(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -235,14 +231,14 @@ sql_query(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_count(doc) -> - ["Tests select_count/[2,3]'s timeout, " - " select_count's functionality will be better tested by other tests " - " such as first."]; +select_count() -> + [{doc, "Tests select_count/[2,3]'s timeout, " + " select_count's functionality will be better tested by other tests " + " such as first."}]. select_count(sute) -> []; select_count(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -257,12 +253,11 @@ select_count(Config) when is_list(Config) -> (catch odbc:select_count(Ref, "SELECT * FROM ", -1)), ok. %%------------------------------------------------------------------------- -first(doc) -> - ["Tests first/[1,2]"]; -first(suite) -> []; +first() -> + [doc, {"Tests first/[1,2]"}]. first(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -284,12 +279,11 @@ first(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -last(doc) -> - ["Tests last/[1,2]"]; -last(suite) -> []; +last() -> + [{doc, "Tests last/[1,2]"}]. last(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -311,12 +305,11 @@ last(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -next(doc) -> - ["Tests next/[1,2]"]; -next(suite) -> []; +next() -> + [{doc, "Tests next/[1,2]"}]. next(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -337,12 +330,11 @@ next(Config) when is_list(Config) -> {'EXIT', {function_clause, _}} = (catch odbc:next(Ref, -1)), ok. %%------------------------------------------------------------------------- -prev(doc) -> - ["Tests prev/[1,2]"]; -prev(suite) -> []; +prev() -> + [{doc, "Tests prev/[1,2]"}]. prev(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -366,12 +358,12 @@ prev(Config) when is_list(Config) -> {'EXIT', {function_clause, _}} = (catch odbc:prev(Ref, -1)), ok. %%------------------------------------------------------------------------- -select_next(doc) -> - ["Tests select/[4,5] with CursorRelation = next "]; +select_next() -> + [{doc, "Tests select/[4,5] with CursorRelation = next "}]. select_next(suit) -> []; select_next(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -407,12 +399,12 @@ select_next(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_relative(doc) -> - ["Tests select/[4,5] with CursorRelation = relative "]; +select_relative() -> + [{doc, "Tests select/[4,5] with CursorRelation = relative "}]. select_relative(suit) -> []; select_relative(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -448,12 +440,12 @@ select_relative(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_absolute(doc) -> - ["Tests select/[4,5] with CursorRelation = absolute "]; +select_absolute() -> + [{doc, "Tests select/[4,5] with CursorRelation = absolute "}]. select_absolute(suit) -> []; select_absolute(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -482,12 +474,11 @@ select_absolute(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -create_table_twice(doc) -> - ["Test what happens if you try to create the same table twice."]; -create_table_twice(suite) -> []; +create_table_twice() -> + [{doc, "Test what happens if you try to create the same table twice."}]. create_table_twice(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -501,12 +492,11 @@ create_table_twice(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -delete_table_twice(doc) -> - ["Test what happens if you try to delete the same table twice."]; -delete_table_twice(suite) -> []; +delete_table_twice() -> + [{doc, "Test what happens if you try to delete the same table twice."}]. delete_table_twice(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -518,12 +508,12 @@ delete_table_twice(Config) when is_list(Config) -> ok. %------------------------------------------------------------------------- -duplicate_key(doc) -> - ["Test what happens if you try to use the same key twice"]; +duplicate_key() -> + [{doc, "Test what happens if you try to use the same key twice"}]. duplicate_key(suit) -> []; duplicate_key(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -539,13 +529,12 @@ duplicate_key(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -not_connection_owner(doc) -> - ["Test what happens if a process that did not start the connection" - " tries to acess it."]; -not_connection_owner(suite) -> []; +not_connection_owner() -> + [{doc, "Test what happens if a process that did not start the connection" + " tries to acess it."}]. not_connection_owner(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), spawn_link(?MODULE, not_owner, [self(), Ref, Table]), @@ -564,12 +553,11 @@ not_owner(Pid, Ref, Table) -> Pid ! continue. %%------------------------------------------------------------------------- -no_result_set(doc) -> - ["Tests what happens if you try to use a function that needs an " - "associated result set when there is none."]; -no_result_set(suite) -> []; +no_result_set() -> + [{doc, "Tests what happens if you try to use a function that needs an " + "associated result set when there is none."}]. no_result_set(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), {error, result_set_does_not_exist} = odbc:first(Ref), {error, result_set_does_not_exist} = odbc:last(Ref), @@ -582,13 +570,11 @@ no_result_set(Config) when is_list(Config) -> odbc:select(Ref, {relative, 2}, 1), ok. %%------------------------------------------------------------------------- -query_error(doc) -> - ["Test what happens if there is an error in the query."]; -query_error(suite) -> - []; +query_error() -> + [{doc, "Test what happens if there is an error in the query."}]. query_error(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -605,15 +591,13 @@ query_error(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -multiple_select_result_sets(doc) -> - ["Test what happens if you have a batch of select queries."]; -multiple_select_result_sets(suite) -> - []; +multiple_select_result_sets() -> + [{doc, "Test what happens if you have a batch of select queries."}]. multiple_select_result_sets(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -640,16 +624,14 @@ multiple_select_result_sets(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -multiple_mix_result_sets(doc) -> - ["Test what happens if you have a batch of select and other type of" - " queries."]; -multiple_mix_result_sets(suite) -> - []; +multiple_mix_result_sets() -> + [{doc, "Test what happens if you have a batch of select and other type of" + " queries."}]. multiple_mix_result_sets(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -674,15 +656,13 @@ multiple_mix_result_sets(Config) when is_list(Config) -> {skip, "multiple result_set not supported"} end. %%------------------------------------------------------------------------- -multiple_result_sets_error(doc) -> - ["Test what happens if one of the batched queries fails."]; -multiple_result_sets_error(suite) -> - []; +multiple_result_sets_error() -> + [{doc, "Test what happens if one of the batched queries fails."}]. multiple_result_sets_error(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -709,15 +689,13 @@ 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) -> - []; +param_insert_tiny_int()-> + [{doc,"Test insertion of tiny ints by parameterized queries."}]. param_insert_tiny_int(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -746,13 +724,11 @@ param_insert_tiny_int(Config) when is_list(Config) -> {skip, "Type tiniyint not supported"} end. %%------------------------------------------------------------------------- -param_insert_small_int(doc)-> - ["Test insertion of small ints by parameterized queries."]; -param_insert_small_int(suite) -> - []; +param_insert_small_int()-> + [{doc,"Test insertion of small ints by parameterized queries."}]. param_insert_small_int(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -778,13 +754,11 @@ param_insert_small_int(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_int(doc)-> - ["Test insertion of ints by parameterized queries."]; -param_insert_int(suite) -> - []; +param_insert_int()-> + [{doc,"Test insertion of ints by parameterized queries."}]. param_insert_int(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -810,13 +784,11 @@ param_insert_int(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_integer(doc)-> - ["Test insertion of integers by parameterized queries."]; -param_insert_integer(suite) -> - []; +param_insert_integer()-> + [{doc,"Test insertion of integers by parameterized queries."}]. param_insert_integer(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -842,13 +814,11 @@ param_insert_integer(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_decimal(doc)-> - ["Test insertion of decimal numbers by parameterized queries."]; -param_insert_decimal(suite) -> - []; +param_insert_decimal()-> + [{doc,"Test insertion of decimal numbers by parameterized queries."}]. param_insert_decimal(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -893,13 +863,11 @@ param_insert_decimal(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_numeric(doc)-> - ["Test insertion of numeric numbers by parameterized queries."]; -param_insert_numeric(suite) -> - []; +param_insert_numeric()-> + [{doc,"Test insertion of numeric numbers by parameterized queries."}]. param_insert_numeric(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -944,13 +912,11 @@ 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) -> - []; +param_insert_char()-> + [{doc,"Test insertion of fixed length string by parameterized queries."}]. param_insert_char(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -980,13 +946,11 @@ param_insert_char(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_character(doc)-> - ["Test insertion of fixed length string by parameterized queries."]; -param_insert_character(suite) -> - []; +param_insert_character()-> + [{doc,"Test insertion of fixed length string by parameterized queries."}]. param_insert_character(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1017,13 +981,11 @@ param_insert_character(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------ -param_insert_char_varying(doc)-> - ["Test insertion of variable length strings by parameterized queries."]; -param_insert_char_varying(suite) -> - []; +param_insert_char_varying()-> + [{doc,"Test insertion of variable length strings by parameterized queries."}]. param_insert_char_varying(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1054,13 +1016,11 @@ param_insert_char_varying(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_character_varying(doc)-> - ["Test insertion of variable length strings by parameterized queries."]; -param_insert_character_varying(suite) -> - []; +param_insert_character_varying()-> + [{doc,"Test insertion of variable length strings by parameterized queries."}]. param_insert_character_varying(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1091,13 +1051,11 @@ param_insert_character_varying(Config) when is_list(Config) -> [{{sql_varchar, 10}, ["1", 2]}])), ok. %%------------------------------------------------------------------------- -param_insert_float(doc)-> - ["Test insertion of floats by parameterized queries."]; -param_insert_float(suite) -> - []; +param_insert_float()-> + [{doc,"Test insertion of floats by parameterized queries."}]. param_insert_float(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1120,7 +1078,7 @@ param_insert_float(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(float_numbers_do_not_match) + ct:fail(float_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1130,13 +1088,11 @@ param_insert_float(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_real(doc)-> - ["Test insertion of real numbers by parameterized queries."]; -param_insert_real(suite) -> - []; +param_insert_real()-> + [{doc,"Test insertion of real numbers by parameterized queries."}]. param_insert_real(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1161,7 +1117,7 @@ param_insert_real(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(real_numbers_do_not_match) + ct:fail(real_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1171,13 +1127,11 @@ param_insert_real(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_double(doc)-> - ["Test insertion of doubles by parameterized queries."]; -param_insert_double(suite) -> - []; +param_insert_double()-> + [{doc,"Test insertion of doubles by parameterized queries."}]. param_insert_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1200,7 +1154,7 @@ param_insert_double(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(double_numbers_do_not_match) + ct:fail(double_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1210,13 +1164,11 @@ param_insert_double(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_mix(doc)-> - ["Test insertion of a mixture of datatypes by parameterized queries."]; -param_insert_mix(suite) -> - []; +param_insert_mix()-> + [{doc,"Test insertion of a mixture of datatypes by parameterized queries."}]. param_insert_mix(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1237,13 +1189,11 @@ param_insert_mix(Config) when is_list(Config) -> odbc:sql_query(Ref, "SELECT * FROM " ++ Table), ok. %%------------------------------------------------------------------------- -param_update(doc)-> - ["Test parameterized update query."]; -param_update(suite) -> - []; +param_update()-> + [{doc,"Test parameterized update query."}]. param_update(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1272,12 +1222,12 @@ param_update(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -delete_nonexisting_row(doc) -> % OTP-5759 - ["Make a delete...where with false conditions (0 rows deleted). ", - "This used to give an error message (see ticket OTP-5759)."]; +delete_nonexisting_row() -> % OTP-5759 + [{doc, "Make a delete...where with false conditions (0 rows deleted). ", + "This used to give an error message (see ticket OTP-5759)."}]. delete_nonexisting_row(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table @@ -1301,13 +1251,11 @@ delete_nonexisting_row(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_delete(doc) -> - ["Test parameterized delete query."]; -param_delete(suite) -> - []; +param_delete() -> + [{doc,"Test parameterized delete query."}]. param_delete(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1336,13 +1284,11 @@ param_delete(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -param_select(doc) -> - ["Test parameterized select query."]; -param_select(suite) -> - []; +param_select() -> + [{doc,"Test parameterized select query."}]. param_select(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1366,13 +1312,11 @@ param_select(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_select_empty_params(doc) -> - ["Test parameterized select query with no parameters."]; -param_select_empty_params(suite) -> - []; +param_select_empty_params() -> + [{doc,"Test parameterized select query with no parameters."}]. param_select_empty_params(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1396,13 +1340,11 @@ param_select_empty_params(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_delete_empty_params(doc) -> - ["Test parameterized delete query with no parameters."]; -param_delete_empty_params(suite) -> - []; +param_delete_empty_params() -> + [{doc,"Test parameterized delete query with no parameters."}]. param_delete_empty_params(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1430,13 +1372,11 @@ param_delete_empty_params(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_integer(doc) -> - ["Test describe_table/[2,3] for integer columns."]; -describe_integer(suite) -> - []; +describe_integer() -> + [{doc,"Test describe_table/[2,3] for integer columns."}]. describe_integer(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1449,13 +1389,11 @@ describe_integer(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_string(doc) -> - ["Test describe_table/[2,3] for string columns."]; -describe_string(suite) -> - []; +describe_string() -> + [{doc,"Test describe_table/[2,3] for string columns."}]. describe_string(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1470,13 +1408,11 @@ describe_string(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_floating(doc) -> - ["Test describe_table/[2,3] for floting columns."]; -describe_floating(suite) -> - []; +describe_floating() -> + [{doc,"Test describe_table/[2,3] for floting columns."}]. describe_floating(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1490,14 +1426,12 @@ describe_floating(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_dec_num(doc) -> - ["Test describe_table/[2,3] for decimal and numerical columns"]; -describe_dec_num(suite) -> - []; +describe_dec_num() -> + [{doc,"Test describe_table/[2,3] for decimal and numerical columns"}]. describe_dec_num(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1511,14 +1445,12 @@ describe_dec_num(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -describe_timestamp(doc) -> - ["Test describe_table/[2,3] for tinmestap columns"]; -describe_timestamp(suite) -> - []; +describe_timestamp() -> + [{doc,"Test describe_table/[2,3] for tinmestap columns"}]. describe_timestamp(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1530,14 +1462,12 @@ describe_timestamp(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_no_such_table(doc) -> - ["Test what happens if you try to describe a table that does not exist."]; -describe_no_such_table(suite) -> - []; +describe_no_such_table() -> + [{doc,"Test what happens if you try to describe a table that does not exist."}]. describe_no_such_table(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {error, _ } = odbc:describe_table(Ref, Table), ok. @@ -1549,10 +1479,10 @@ describe_no_such_table(Config) when is_list(Config) -> is_driver_error(Error) -> case is_list(Error) of true -> - test_server:format("Driver error ~p~n", [Error]), + ct:pal("Driver error ~p~n", [Error]), ok; false -> - test_server:fail(Error) + ct:fail(Error) end. is_supported_multiple_resultsets(sqlserver) -> true; diff --git a/lib/odbc/test/odbc_start_SUITE.erl b/lib/odbc/test/odbc_start_SUITE.erl index f055eeb60e..310b92ca29 100644 --- a/lib/odbc/test/odbc_start_SUITE.erl +++ b/lib/odbc/test/odbc_start_SUITE.erl @@ -49,6 +49,7 @@ init_per_suite(Config) -> _ -> %% Make sure odbc is not already started odbc:stop(), + ct:timetrap(?TIMEOUT), [{tableName, odbc_test_lib:unique_table_name()} | Config] end end. @@ -74,11 +75,9 @@ end_per_suite(_Config) -> %% variable, but should NOT alter/remove any existing entries. %% Description: Initialization before each test case %%-------------------------------------------------------------------- -init_per_testcase(_TestCase, Config0) -> - test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), - Config = lists:keydelete(watchdog, 1, Config0), - Dog = test_server:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:pal("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), + Config. %%-------------------------------------------------------------------- %% Function: end_per_testcase(TestCase, Config) -> _ @@ -88,15 +87,8 @@ init_per_testcase(_TestCase, Config0) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(_TestCase, Config) -> - Dog = ?config(watchdog, Config), - case Dog of - undefined -> - ok; - _ -> - test_server:timetrap_cancel(Dog) - end. - +end_per_testcase(_TestCase, _Config) -> + ok. %%-------------------------------------------------------------------- %% Function: all(Clause) -> TestCases %% Clause - atom() - suite | doc @@ -135,10 +127,8 @@ app(Config) when is_list(Config) -> appup(Config) when is_list(Config) -> ok = ?t:appup_test(odbc). -start(doc) -> - ["Test start/stop of odbc"]; -start(suite) -> - []; +start() -> + [{doc,"Test start/stop of odbc"}]. start(Config) when is_list(Config) -> PlatformOptions = odbc_test_lib:platform_options(), {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), @@ -153,9 +143,9 @@ start(Config) when is_list(Config) -> start_odbc(transient), start_odbc(permanent); {error, odbc_not_started} -> - test_server:fail(start_failed); + ct:fail(start_failed); Error -> - test_server:format("Connection failed: ~p~n", [Error]), + ct:pal("Connection failed: ~p~n", [Error]), {skip, "ODBC is not properly setup"} end. @@ -166,13 +156,12 @@ start_odbc(Type) -> ok = odbc:disconnect(Ref), odbc:stop(); {error, odbc_not_started} -> - test_server:fail(start_failed) + ct:fail(start_failed) end. -long_connection_line(doc)-> - ["Test a connection line longer than 127 characters"]; -long_connection_line(suite) -> []; +long_connection_line()-> + [{doc,"Test a connection line longer than 127 characters"}]. long_connection_line(_Config) -> odbc:start(), String133 = "unknown_odbc_parameter=01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", diff --git a/lib/odbc/test/odbc_test_lib.erl b/lib/odbc/test/odbc_test_lib.erl index 37c2249303..cf82d4d32a 100644 --- a/lib/odbc/test/odbc_test_lib.erl +++ b/lib/odbc/test/odbc_test_lib.erl @@ -61,13 +61,13 @@ odbc_check() -> end. check_row_count(Count, Count) -> - test_server:format("Correct row count Count: ~p~n", [Count]), + ct:pal("Correct row count Count: ~p~n", [Count]), true; check_row_count(_, undefined) -> - test_server:format("Undefined row count ~n", []), + ct:pal("Undefined row count ~n", []), true; check_row_count(Expected, Count) -> - test_server:format("Incorrect row count Expected ~p Got ~p~n", + ct:pal("Incorrect row count Expected ~p Got ~p~n", [Expected, Count]), false. diff --git a/lib/parsetools/doc/src/yecc.xml b/lib/parsetools/doc/src/yecc.xml index 6ec9fb5f0e..9188bd2a22 100644 --- a/lib/parsetools/doc/src/yecc.xml +++ b/lib/parsetools/doc/src/yecc.xml @@ -201,7 +201,7 @@ <p>The grammar starts with an optional <c>header</c> section. The header is put first in the generated file, before the module declaration. The purpose of the header is to provide a means to - make the documentation generated by <c>EDoc</c> look nicer. Each + make the documentation generated by EDoc look nicer. Each header line should be enclosed in double quotes, and newlines will be inserted between the lines. For example:</p> <code> diff --git a/lib/parsetools/src/yecc.erl b/lib/parsetools/src/yecc.erl index 1b426141a1..f6b80eb1b4 100644 --- a/lib/parsetools/src/yecc.erl +++ b/lib/parsetools/src/yecc.erl @@ -1978,7 +1978,8 @@ output_goto(St, [{_Nonterminal, []} | Go], StateInfo) -> output_goto(St, Go, StateInfo); output_goto(St0, [{Nonterminal, List} | Go], StateInfo) -> F = function_name(yeccgoto, Nonterminal), - St10 = output_goto1(St0, List, F, StateInfo, true), + St05 = fwrite(St0, <<"-dialyzer({nowarn_function, ~w/7}).\n">>, [F]), + St10 = output_goto1(St05, List, F, StateInfo, true), St = output_goto_fini(F, Nonterminal, St10), output_goto(St, Go, StateInfo); output_goto(St, [], _StateInfo) -> diff --git a/lib/parsetools/test/yecc_SUITE.erl b/lib/parsetools/test/yecc_SUITE.erl index 3710569aba..e91ddb11d1 100644 --- a/lib/parsetools/test/yecc_SUITE.erl +++ b/lib/parsetools/test/yecc_SUITE.erl @@ -342,7 +342,7 @@ syntax(Config) when is_list(Config) -> {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}], []} = compile:file(Parserfile1, [basic_validation,return]), ?line L1 = 31 + SzYeccPre, - ?line L2 = 38 + SzYeccPre + ?line L2 = 39 + SzYeccPre end(), %% Bad macro in action. OTP-7224. @@ -360,7 +360,7 @@ syntax(Config) when is_list(Config) -> {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}], []} = compile:file(Parserfile1, [basic_validation,return]), ?line L1 = 31 + SzYeccPre, - ?line L2 = 38 + SzYeccPre + ?line L2 = 39 + SzYeccPre end(), %% Check line numbers. OTP-7224. @@ -1623,7 +1623,7 @@ otp_7292(Config) when is_list(Config) -> [{_,[{16,_,{unused_function,{foo,0}}}]}]} = compile:file(Parserfile1, [basic_validation, return]), L1 = 41 + SzYeccPre, - L2 = 48 + SzYeccPre + L2 = 49 + SzYeccPre end(), YeccPre = filename:join(Dir, "yeccpre.hrl"), @@ -1641,7 +1641,7 @@ otp_7292(Config) when is_list(Config) -> [{_,[{16,_,{unused_function,{foo,0}}}]}]} = compile:file(Parserfile1, [basic_validation, return]), ?line L1 = 40 + SzYeccPre, - ?line L2 = 47 + SzYeccPre + ?line L2 = 48 + SzYeccPre end(), file:delete(YeccPre), diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml index 6df4924d0a..25d983afd3 100644 --- a/lib/reltool/doc/src/notes.xml +++ b/lib/reltool/doc/src/notes.xml @@ -546,8 +546,8 @@ <p>The handling of applications included in releases has been improved. Applications that are required to be started before other applications in a release are now - automatically included in the release. The <c>kernel</c> - and <c>stdlib</c> applications are always included as + automatically included in the release. The Kernel + and STDLIB applications are always included as they are mandatory.</p> <p>Applications that are (explicitly or implicitly) included in a release are now automatically included as diff --git a/lib/reltool/doc/src/part.xml b/lib/reltool/doc/src/part.xml index 5904084879..e608d548ea 100644 --- a/lib/reltool/doc/src/part.xml +++ b/lib/reltool/doc/src/part.xml @@ -31,7 +31,7 @@ <rev>%VSN%</rev> </header> <description> - <p><c>Reltool</c> is a release management tool. It analyses a given + <p>Reltool is a release management tool. It analyses a given Erlang/OTP installation and determines various dependencies between applications. The <c>graphical</c> frontend depicts the dependencies and enables interactive customization of a diff --git a/lib/reltool/doc/src/ref_man.xml b/lib/reltool/doc/src/ref_man.xml index 38f270b79a..1684f075ff 100644 --- a/lib/reltool/doc/src/ref_man.xml +++ b/lib/reltool/doc/src/ref_man.xml @@ -31,7 +31,7 @@ <rev>%VSN%</rev> </header> <description> - <p><c>Reltool</c> is a release management tool. It analyses a given + <p>Reltool is a release management tool. It analyses a given Erlang/OTP installation and determines various dependencies between applications. The <c>graphical</c> frontend depicts the dependencies and enables interactive customization of a diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml index 38448e7961..5bfbee966b 100644 --- a/lib/reltool/doc/src/reltool.xml +++ b/lib/reltool/doc/src/reltool.xml @@ -47,7 +47,7 @@ <p>The tool uses an installed Erlang/OTP system as input. <c>root_dir</c> is the root directory of the analysed system and - it defaults to the system executing <c>reltool</c>. Applications + it defaults to the system executing Reltool. Applications may also be located outside <c>root_dir</c>. <c>lib_dirs</c> defines library directories where additional applications may reside and it defaults to the directories @@ -56,7 +56,7 @@ <p>An application directory <c>AppDir</c> under a library directory is recognized by the existence of an <c>AppDir/ebin</c> - directory. If this does not exist, <c>reltool</c> will not + directory. If this does not exist, Reltool will not consider <c>AppDir</c> at all when looking for applications.</p> <p>It is recommended that application directories are named as the @@ -81,14 +81,14 @@ <tag><c>config</c></tag> <item> <p>This is the main option and it controls the configuration - of <c>reltool</c>. It can either be a <c>sys</c> tuple or + of Reltool. It can either be a <c>sys</c> tuple or a name of a <c>file</c> containing a sys tuple.</p> </item> <tag><c>trap_exit</c></tag> <item> <p>This option controls the error handling behavior of - <c>reltool</c>. By default the window processes traps + Reltool. By default the window processes traps exit, but this behavior can altered by setting <c>trap_exit</c> to <c>false</c>.</p> </item> diff --git a/lib/reltool/doc/src/reltool_intro.xml b/lib/reltool/doc/src/reltool_intro.xml index 8e232b8838..2980ad7977 100644 --- a/lib/reltool/doc/src/reltool_intro.xml +++ b/lib/reltool/doc/src/reltool_intro.xml @@ -34,7 +34,7 @@ <rev>%VSN%</rev> <file>reltool_intro.xml</file> </header> - <p><c>Reltool</c> is a release management tool. It analyses a given + <p>Reltool is a release management tool. It analyses a given Erlang/OTP installation and determines various dependencies between applications. The <c>graphical</c> frontend depicts the dependencies and enables interactive customization of a target system. The backend provides a @@ -82,7 +82,7 @@ and about the Erlang/OTP development system:</p> <list type="bulleted"> <item> - <p>the Reference Manual of <c>Reltool</c></p> + <p>the Reference Manual of Reltool</p> </item> <item> <p>the Erlang/OTP <c>System Principles</c></p> diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl index 9ac22b9450..89b5f3a997 100644 --- a/lib/reltool/src/reltool.hrl +++ b/lib/reltool/src/reltool.hrl @@ -154,19 +154,19 @@ -record(app_info, { - description = "" :: string(), - id = "" :: string(), - vsn = "" :: app_vsn(), - modules = [] :: [mod_name()], - maxP = infinity :: integer() | infinity, - maxT = infinity :: integer() | infinity, - registered = [] :: [atom()], - incl_apps = [] :: [app_name()], - applications = [] :: [app_name()], - env = [] :: [{atom(), term()}], - mod = undefined :: {mod_name(), [term()]} | undefined, - start_phases = undefined :: [{atom(), term()}] | undefined, - runtime_dependencies = [] :: [string()] + description = "" :: '_' | string(), + id = "" :: '_' | string(), + vsn = "" :: '_' | app_vsn(), + modules = [] :: '_' | [mod_name()], + maxP = infinity :: '_' | integer() | infinity, + maxT = infinity :: '_' | integer() | infinity, + registered = [] :: '_' | [atom()], + incl_apps = [] :: '_' | '$3' | [app_name()], + applications = [] :: '_' | '$2' | [app_name()], + env = [] :: '_' | [{atom(), term()}], + mod = undefined :: '_' | {mod_name(), [term()]} | undefined, + start_phases = undefined :: '_' | [{atom(), term()}] | undefined, + runtime_dependencies = [] :: '_' | [string()] }). -record(regexp, {source, compiled}). diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl index 21a4485f94..89e90670cf 100644 --- a/lib/reltool/src/reltool_server.erl +++ b/lib/reltool/src/reltool_server.erl @@ -530,9 +530,14 @@ analyse(#state{sys=Sys} = S, Apps, Status) -> %% Write all #app to app_tab and all #mod to mod_tab. Status2 = apps_init_is_included(S, Apps, RelApps, Status), + %% For each application that is not (directly or indirectly) part + %% of a release, but still has #app.is_included==true, propagate + %% is_included to the dependencies specified in the .app files. + app_propagate_is_included(S), + %% For each module that has #mod.is_included==true, propagate %% is_included to the modules it uses. - propagate_is_included(S), + mod_propagate_is_included(S), %% Insert reverse dependencies - i.e. for each %% #mod{name=Mod, uses_mods=[UsedMod]}, @@ -565,31 +570,34 @@ apps_in_rels(Rels, Apps) -> apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps) -> Mandatory = [{RelName, kernel}, {RelName, stdlib}], - Other = + Explicit0 = [{RelName, AppName} || #rel_app{name=AppName} <- RelApps], + Explicit = Mandatory ++ Explicit0, + Deps = [{RelName, AppName} || RA <- RelApps, - AppName <- [RA#rel_app.name | + AppName <- + case lists:keyfind(RA#rel_app.name, + #app.name, + Apps) of + App=#app{info = #app_info{applications = AA}} -> %% Included applications in rel shall overwrite included %% applications in .app. I.e. included applications in %% .app shall only be used if it is not defined in rel. - case RA#rel_app.incl_apps of - undefined -> - case lists:keyfind(RA#rel_app.name, - #app.name, - Apps) of - #app{info = #app_info{incl_apps = IA}} -> - IA; - false -> - reltool_utils:throw_error( - "Release ~tp uses non existing " - "application ~w", - [RelName,RA#rel_app.name]) - end; - IA -> - IA - end], - not lists:keymember(AppName, 2, Mandatory)], - more_apps_in_rels(Mandatory ++ Other, Apps, []). + IA = case RA#rel_app.incl_apps of + undefined -> + (App#app.info)#app_info.incl_apps; + RelIA -> + RelIA + end, + AA ++ IA; + false -> + reltool_utils:throw_error( + "Release ~tp uses non existing " + "application ~w", + [RelName,RA#rel_app.name]) + end, + not lists:keymember(AppName, 2, Explicit)], + more_apps_in_rels(Deps, Apps, Explicit). more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> case lists:member(RA, Acc) of @@ -597,8 +605,8 @@ more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> more_apps_in_rels(RelApps, Apps, Acc); false -> case lists:keyfind(AppName, #app.name, Apps) of - #app{info = #app_info{applications = InfoApps}} -> - Extra = [{RelName, N} || N <- InfoApps], + #app{info = #app_info{applications = AA, incl_apps=IA}} -> + Extra = [{RelName, N} || N <- AA++IA], Acc2 = more_apps_in_rels(Extra, Apps, [RA | Acc]), more_apps_in_rels(RelApps, Apps, Acc2); false -> @@ -610,7 +618,6 @@ more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> more_apps_in_rels([], _Apps, Acc) -> Acc. - apps_init_is_included(S, Apps, RelApps, Status) -> lists:foldl(fun(App, AccStatus) -> app_init_is_included(S, App, RelApps, AccStatus) @@ -745,6 +752,100 @@ false_to_undefined(Bool) -> _ -> Bool end. +get_no_rel_apps_and_dependencies(S) -> + ets:select(S#state.app_tab, [{#app{name='$1', + is_included=true, + info=#app_info{applications='$2', + incl_apps='$3', + _='_'}, + rels=[], + _='_'}, + [], + [{{'$1','$2','$3'}}]}]). + +app_propagate_is_included(S) -> + lists:foreach( + fun({AppName,DepNames1,DepNames2}) -> + app_mark_is_included(S,AppName,DepNames1++DepNames2) + end, + get_no_rel_apps_and_dependencies(S)). + +app_mark_is_included(#state{app_tab=AppTab, mod_tab=ModTab, sys=Sys}=S,UsedByName,[AppName|AppNames]) -> + case ets:lookup(AppTab, AppName) of + [A] -> + case A#app.is_included of + undefined -> + %% Not yet marked => mark and propagate + A2 = + case A#app.incl_cond of + include -> + A#app{is_pre_included = true, + is_included = true}; + exclude -> + A#app{is_pre_included = false, + is_included = false}; + AppInclCond when AppInclCond==undefined; + AppInclCond==derived -> + A#app{is_included = true} + end, + ets:insert(AppTab, A2), + + ModCond = + case A#app.mod_cond of + undefined -> Sys#sys.mod_cond; + _ -> A#app.mod_cond + end, + Filter = + fun(M) -> + case ModCond of + all -> true; + app -> M#mod.is_app_mod; + ebin -> M#mod.is_ebin_mod; + derived -> false; + none -> false + end + end, + Mods = lists:filter(Filter, A#app.mods), + %% Mark the modules of this app, but no need to go + %% recursive on modules since this is done in + %% mod_mark_is_included. + [case M#mod.is_included of + undefined -> + M2 = + case M#mod.incl_cond of + include -> + M#mod{is_pre_included = true, + is_included = true}; + exclude -> + M#mod{is_pre_included = false, + is_included = false}; + ModInclCond when ModInclCond==undefined; + ModInclCond==derived -> + M#mod{is_included = true} + end, + ets:insert(ModTab, M2); + _ -> + ok + end || M <- Mods], + + %% Go recursive on dependencies + #app{info=#app_info{applications=DepNames1, + incl_apps=DepNames2}} = A, + app_mark_is_included(S,AppName,DepNames1++DepNames2); + _ -> + %% Already marked + ok + end; + [] -> + %% Missing app + reltool_utils:throw_error( + "Application ~tp uses non existing application ~w", + [UsedByName,AppName]) + end, + app_mark_is_included(S, UsedByName, AppNames); +app_mark_is_included(_S, _UsedByName, []) -> + ok. + %% Return the list for {ModName, UsesModNames} for all modules where %% #mod.is_included==true. get_all_mods_and_dependencies(S) -> @@ -755,7 +856,7 @@ get_all_mods_and_dependencies(S) -> [], [{{'$1','$2'}}]}]). -propagate_is_included(S) -> +mod_propagate_is_included(S) -> case lists:flatmap( fun({ModName,UsesModNames}) -> mod_mark_is_included(S,ModName,UsesModNames,[]) diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl index e9bd46fb27..e8dfea94da 100644 --- a/lib/reltool/test/reltool_server_SUITE.erl +++ b/lib/reltool/test/reltool_server_SUITE.erl @@ -142,6 +142,7 @@ all() -> save_config, dependencies, mod_incl_cond_derived, + dep_in_app_not_xref, use_selected_vsn, use_selected_vsn_relative_path, non_standard_vsn_id, @@ -408,7 +409,6 @@ create_release_sort(Config) -> {app,tools,[{mod_cond,app},{incl_cond,include}]} ]}, %% Generate release - ?msym({ok, {release, {RelName1, RelVsn}, {erts, _}, [{kernel, _}, @@ -2304,6 +2304,7 @@ dependencies(Config) -> ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test that incl_cond on mod level overwrites mod_cond on app level %% Uses same test applications as dependencies/1 above mod_incl_cond_derived(Config) -> @@ -2346,6 +2347,40 @@ mod_incl_cond_derived(Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% ERL-167, OTP-11993: For applications that are not included in a +%% release spec ('rel'), dependencies in the .app files are not +%% considered - only those found with xref. +dep_in_app_not_xref(Config) -> + RelName = "Just testing...", + RelVsn = "1.0", + Sys = + {sys, + [ + {lib_dirs,[filename:join(datadir(Config),"dep_in_app_not_xref")]}, + {incl_cond,exclude}, + {incl_archive_filters,[]}, + {erts,[{incl_cond,exclude}]}, + {boot_rel, RelName}, + {rel, RelName, RelVsn, [kernel, stdlib]}, + {app,kernel,[{incl_cond,include}]}, + {app,stdlib,[{incl_cond,include}]}, + {app,x,[{incl_cond,include}]}, + {app,y,[{incl_cond,derived}]}, + {app,z,[{incl_cond,derived}]} + ]}, + + TargetDir = filename:join([?WORK_DIR, "target_dep_in_app_not_xref"]), + ?m(ok, reltool_utils:recursive_delete(TargetDir)), + ?m(ok, file:make_dir(TargetDir)), + ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, Sys}])]), + ok = ?m(ok, reltool:create_target([{config, Sys}], TargetDir)), + ?log("~p~n",[file:list_dir(filename:join([TargetDir,"lib"]))]), + + ?m(true, filelib:is_dir(filename:join([TargetDir,"lib","y-1.0"]))), + ?m(true, filelib:is_dir(filename:join([TargetDir,"lib","z-1.0"]))), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% use_selected_vsn(Config) -> LibDir1 = filename:join(datadir(Config),"use_selected_vsn"), B1Dir = filename:join(LibDir1,"b-1.0"), diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app new file mode 100644 index 0000000000..fe922e3a41 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app @@ -0,0 +1,7 @@ +% -*-erlang-*- +{application, x, + [{description, "Main application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [x1]}, + {registered, []}, + {applications, [kernel, stdlib, y]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl new file mode 100644 index 0000000000..bf1e7f9279 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl @@ -0,0 +1,5 @@ +-module(x1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app new file mode 100644 index 0000000000..a21cfe6c21 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app @@ -0,0 +1,8 @@ +% -*-erlang-*- +{application, y, + [{description, "Library application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [y1]}, + {registered, []}, + {applications, [kernel, stdlib]}, + {included_applications, [z]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl new file mode 100644 index 0000000000..902a7e21f3 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl @@ -0,0 +1,5 @@ +-module(y1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app new file mode 100644 index 0000000000..437a0968e9 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app @@ -0,0 +1,7 @@ +% -*-erlang-*- +{application, z, + [{description, "Library application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [z1]}, + {registered, []}, + {applications, [kernel, stdlib]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl new file mode 100644 index 0000000000..97ef90b87f --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl @@ -0,0 +1,5 @@ +-module(z1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml index 14a81b2293..db04bfdf7b 100644 --- a/lib/runtime_tools/doc/src/dbg.xml +++ b/lib/runtime_tools/doc/src/dbg.xml @@ -78,7 +78,7 @@ <p>Pseudo function that by means of a <c>parse_transform</c> translates the <em>literal</em><c>fun()</c> typed as parameter in the function call to a match specification as described in - the <c>match_spec</c> manual of <c>ERTS</c> users guide. + the <c>match_spec</c> manual of ERTS users guide. (with literal I mean that the <c>fun()</c> needs to textually be written as the parameter of the function, it cannot be held in a variable which in turn is passed to the diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml index 6bec7cb9ca..b6dfb2dd28 100644 --- a/lib/runtime_tools/doc/src/notes.xml +++ b/lib/runtime_tools/doc/src/notes.xml @@ -668,7 +668,7 @@ also been extended. </item><item> The <c>configure</c> scripts of <c>erl_interface</c> and <c>odbc</c> now search for thread libraries and thread library quirks the - same way as <c>erts</c> do. </item><item> The + same way as ERTS do. </item><item> The <c>configure</c> script of the <c>odbc</c> application now also looks for odbc libraries in <c>lib64</c> and <c>lib/64</c> directories when building on a 64-bit diff --git a/lib/sasl/doc/src/alarm_handler.xml b/lib/sasl/doc/src/alarm_handler.xml index 8550a88b28..4160757164 100644 --- a/lib/sasl/doc/src/alarm_handler.xml +++ b/lib/sasl/doc/src/alarm_handler.xml @@ -54,7 +54,7 @@ format is defined by the user. For example, an event handler for SNMP can be defined, together with an alarm Management Information Base (MIB).</p> - <p>The alarm handler is part of the <c>SASL</c> application.</p> + <p>The alarm handler is part of the SASL application.</p> <p>When writing new event handlers for the alarm handler, the following events must be handled:</p> <taglist> @@ -76,7 +76,7 @@ {NewHandler, Args})</c>. <c>NewHandler:init({Args, {alarm_handler, Alarms}})</c> is called. For more details, see <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso> - in <c>STDLIB</c>.</p> + in STDLIB.</p> </description> <funcs> diff --git a/lib/sasl/doc/src/appup.xml b/lib/sasl/doc/src/appup.xml index 6fbdcb9f5b..a43a966dcb 100644 --- a/lib/sasl/doc/src/appup.xml +++ b/lib/sasl/doc/src/appup.xml @@ -362,12 +362,12 @@ point_of_no_return</pre> system is restarted.</p> <pre> restart_new_emulator</pre> - <p>This instruction is used when the application <c>ERTS</c>, - <c>Kernel</c>, <c>STDLIB</c>, or <c>SASL</c> is + <p>This instruction is used when the application ERTS, + Kernel, STDLIB, or SASL is upgraded. It shuts down the current emulator and starts a new one. All processes are terminated gracefully, and the new - version of <c>ERTS</c>, <c>Kernel</c>, <c>STDLIB</c>, and - <c>SASL</c> are used when the emulator restarts. + version of ERTS, Kernel, STDLIB, and + SASL are used when the emulator restarts. Only one <c>restart_new_emulator</c> instruction is allowed in the <c>relup</c> file, and it must be placed first. <seealso marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seealso> @@ -385,7 +385,7 @@ restart_new_emulator</pre> <warning> <p>As stated earlier, instruction <c>restart_new_emulator</c> causes the emulator to be restarted with new versions of - <c>ERTS</c>, <c>Kernel</c>, <c>STDLIB</c>, and <c>SASL</c>. + ERTS>, Kernel, STDLIB, and SASL. However, all other applications do at startup run their old versions in this new emulator. This is usually no problem, but every now and then incompatible changes occur to the diff --git a/lib/sasl/doc/src/error_logging.xml b/lib/sasl/doc/src/error_logging.xml index 8464a41ff9..4b2c960bbb 100644 --- a/lib/sasl/doc/src/error_logging.xml +++ b/lib/sasl/doc/src/error_logging.xml @@ -32,20 +32,20 @@ <rev>B</rev> <file>error_logging.xml</file> </header> - <p>The <c>SASL</c> application introduces three types of reports:</p> + <p>The SASL application introduces three types of reports:</p> <list type="bulleted"> <item>Supervisor report</item> <item>Progress report</item> <item>Crash report</item> </list> - <p>When the <c>SASL</c> application is started, it adds a handler that + <p>When the SASL application is started, it adds a handler that formats and writes these reports, as specified in the configuration - parameters for <c>SASL</c>, that is, the environment variables - in the <c>SASL</c> application specification, which is found in the - <c>.app</c> file of <c>SASL</c>. For details, see the + parameters for SASL, that is, the environment variables + in the SASL application specification, which is found in the + <c>.app</c> file of SASL. For details, see the <seealso marker="sasl_app"><c>sasl(6)</c></seealso> application in the Reference Manual and the <seealso marker="kernel:app"><c>app(4)</c></seealso> - file in the <c>Kernel</c> Reference Manual.</p> + file in the Kernel Reference Manual.</p> <section> <title>Supervisor Report</title> @@ -180,14 +180,14 @@ <p>The report browser is used to browse and format error reports written by the error logger handler <seealso marker="stdlib:log_mf_h"><c>log_mf_h</c></seealso> - defined in <c>STDLIB</c>.</p> + defined in STDLIB.</p> <p>The <c>log_mf_h</c> handler writes all reports to a report logging directory, which is specified when - configuring the <c>SASL</c> application.</p> + configuring the SASL application.</p> <p>If the report browser is used offline, the reports can be copied to another directory specified when starting the browser. If no such directory - is specified, the browser reads reports from the <c>SASL</c> + is specified, the browser reads reports from the SASL <c>error_logger_mf_dir</c>.</p> <section> diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml index dae73f8b23..bd713af932 100644 --- a/lib/sasl/doc/src/notes.xml +++ b/lib/sasl/doc/src/notes.xml @@ -824,7 +824,7 @@ <list> <item> <p>Use an infinity timeout in all calls to - <c>gen_server:call()</c> in the <c>sasl</c> + <c>gen_server:call()</c> in the SASL application.</p> <p> Own Id: OTP-8506 Aux Id: seq11509 </p> diff --git a/lib/sasl/doc/src/part.xml b/lib/sasl/doc/src/part.xml index f531ed2dea..659710487e 100644 --- a/lib/sasl/doc/src/part.xml +++ b/lib/sasl/doc/src/part.xml @@ -30,7 +30,7 @@ <file>part.xml</file> </header> <description> - <p>The System Architecture Support Libraries <c>SASL</c> application + <p>The System Architecture Support Libraries SASL application provides support for alarm handling, release handling, and related functions.</p> </description> diff --git a/lib/sasl/doc/src/rb.xml b/lib/sasl/doc/src/rb.xml index 1ce19046eb..d5df4fd345 100644 --- a/lib/sasl/doc/src/rb.xml +++ b/lib/sasl/doc/src/rb.xml @@ -38,7 +38,7 @@ <p>The Report Browser (RB) tool is used to browse and format error reports written by the error logger handler <seealso marker="stdlib:log_mf_h"><c>log_mf_h</c></seealso> - in <c>STDLIB</c>.</p> + in STDLIB.</p> </description> <funcs> @@ -62,7 +62,7 @@ reports that match that filter.</p> <p>The reports are matched using the <seealso marker="stdlib:proplists"><c>proplists</c></seealso> - module in <c>STDLIB</c>. The report must be a proplist + module in STDLIB. The report must be a proplist to be matched against any of the filters.</p> <p>If the filter has the form <c>{Key, RegExp, re}</c>, the report must contain an element with key equal to <c>Key</c> and @@ -102,7 +102,7 @@ </list> <p>For a definition of valid regular expressions and options, see the <seealso marker="stdlib:re"><c>re</c></seealso> module in - <c>STDLIB</c> and in particular function <c>re:run/3</c>.</p> + STDLIB and in particular function <c>re:run/3</c>.</p> <p>For details about data type <c>mp()</c>, see <seealso marker="stdlib:re#type-mp"><c>re:mp()</c></seealso>.</p> </desc> diff --git a/lib/sasl/doc/src/ref_man.xml b/lib/sasl/doc/src/ref_man.xml index 42045df5ec..78cf1eb26e 100644 --- a/lib/sasl/doc/src/ref_man.xml +++ b/lib/sasl/doc/src/ref_man.xml @@ -30,7 +30,7 @@ <file>application.xml</file> </header> <description> - <p>The <c>SASL</c> application provides support for alarm handling, + <p>The SASL application provides support for alarm handling, release handling, and related functions.</p> </description> <xi:include href="sasl_app.xml"/> diff --git a/lib/sasl/doc/src/rel.xml b/lib/sasl/doc/src/rel.xml index d6558c06b4..9356b2cd47 100644 --- a/lib/sasl/doc/src/rel.xml +++ b/lib/sasl/doc/src/rel.xml @@ -59,7 +59,7 @@ <tag><c>Vsn = string()</c></tag> <item><p>Release version.</p></item> <tag><c>EVsn = string()</c></tag> - <item><p><c>ERTS</c> version the release is intended for.</p></item> + <item><p>ERTS version the release is intended for.</p></item> <tag><c>Application = atom()</c></tag> <item><p>Name of an application included in the release.</p></item> <tag><c>AppVsn = string()</c></tag> @@ -82,8 +82,8 @@ to the same value as in the application resource file.</p></item> </taglist> <note> - <p>The list of applications must contain the <c>Kernel</c> and - <c>STDLIB</c> applications.</p> + <p>The list of applications must contain the Kernel and + STDLIB applications.</p> </note> </section> diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml index bcbc5f5339..8f073807fb 100644 --- a/lib/sasl/doc/src/release_handler.xml +++ b/lib/sasl/doc/src/release_handler.xml @@ -31,7 +31,7 @@ <module>release_handler</module> <modulesummary>Unpacking and Installation of Release Packages</modulesummary> <description> - <p>The <em>release handler</em> process belongs to the <c>SASL</c> + <p>The <em>release handler</em> process belongs to the SASL application, which is responsible for <em>release handling</em>, that is, unpacking, installation, and removal of release packages.</p> <p>An introduction to release handling and an example is provided in @@ -44,7 +44,7 @@ directory of the previous version of the release, where <c>$ROOT</c> is the installation root directory, <seealso marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seealso>. - Another <c>releases</c> directory can be specified using the <c>SASL</c> + Another <c>releases</c> directory can be specified using the SASL configuration parameter <c>releases_dir</c> or the OS environment variable <c>RELDIR</c>. The release handler must have write access to this directory to install the new release. @@ -56,7 +56,7 @@ <item>A boot script, <c>Name.boot</c></item> </list> <p>The <c>.rel</c> file contains information about the release: its name, - version, and which <c>ERTS</c> and application versions it uses.</p> + version, and which ERTS and application versions it uses.</p> <p>A release package can also contain:</p> <list type="bulleted"> <item>A release upgrade file, <c>relup</c></item> @@ -115,7 +115,7 @@ <em>System Documentation</em>. In this case, the system configuration file <c>sys.config</c> is mandatory.</p> <p>The installation of a new release can restart the system. Which - program to use is specified by the <c>SASL</c> configuration + program to use is specified by the SASL configuration parameter <c>start_prg</c>, which defaults to <c>$ROOT/bin/start</c>.</p> <p>The emulator restart on Windows NT expects that the system is @@ -132,7 +132,7 @@ is made permanent.</p> <p>The release handler at a node running on a diskless machine, or with a read-only file system, must be configured accordingly - using the following <c>SASL</c> configuration parameters (for + using the following SASL configuration parameters (for details, see <seealso marker="sasl_app">sasl(6)</seealso>):</p> <taglist> <tag><c>masters</c></tag> @@ -287,7 +287,7 @@ returned, the emulator is restarted before the upgrade instructions are executed. This occurs if the emulator or any of the applications - <c>Kernel</c>, <c>STDLIB</c>, or <c>SASL</c> + Kernel, STDLIB, or SASL are updated. The new emulator version and these core applications execute after the restart. For all other applications the old versions are @@ -310,13 +310,13 @@ <tag><c>code_change_timeout</c></tag> <item><p>Defines the time-out for all calls to - <seealso marker="stdlib:sys#change_code/4"><c>stdlib:sys:change_code</c></seealso>. + <seealso marker="stdlib:sys#change_code/4"><c>sys:change_code</c></seealso>. If no value is specified or <c>default</c> is specified, the default value defined in <c>sys</c> is used.</p></item> <tag><c>suspend_timeout</c></tag> <item><p>Defines the time-out for all calls to - <seealso marker="stdlib:sys#suspend/1"><c>stdlib:sys:suspend</c></seealso>. + <seealso marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seealso>. If no value is specified, the values defined by the <c>Timeout</c> parameter of the <c>upgrade</c> or <c>suspend</c> instructions are used. If <c>default</c> is specified, the default value defined in @@ -342,7 +342,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). => {ok,NewVsn}</code> <p>If <c>NewVsn</c> is installed with option <c>{update_paths,true}</c>, then - <seealso marker="kernel:code#lib_dir/1"><c>kernel:code:lib_dir(myapp)</c></seealso> + <seealso marker="kernel:code#lib_dir/1"><c>code:lib_dir(myapp)</c></seealso> returns <c>/home/user/myapp-1.0</c>.</p></item> </taglist> <note> @@ -801,7 +801,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). is an atom named from the Posix error codes, such as <c>enoent</c>, <c>eacces</c>, or <c>eisdir</c>. See <seealso marker="kernel:file"><c>file(3)</c></seealso> - in <c>Kernel</c>.</p></item> + in Kernel.</p></item> <tag><c>Posix</c></tag> <item><p>Some file operation failed, as for the previous item in the list.</p></item> diff --git a/lib/sasl/doc/src/sasl_app.xml b/lib/sasl/doc/src/sasl_app.xml index 52f449c2a8..0576397f9b 100644 --- a/lib/sasl/doc/src/sasl_app.xml +++ b/lib/sasl/doc/src/sasl_app.xml @@ -31,17 +31,17 @@ <app>sasl</app> <appsummary>The SASL application</appsummary> <description> - <p>The <c>SASL</c> application provides the following services:</p> + <p>The SASL application provides the following services:</p> <list type="bulleted"> <item><c>alarm_handler</c></item> <item><c>rb</c></item> <item><c>release_handler</c></item> <item><c>systools</c></item> </list> - <p>The <c>SASL</c> application also includes <c>error_logger</c> event - handlers for formatting <c>SASL</c> error and crash reports.</p> + <p>The SASL application also includes <c>error_logger</c> event + handlers for formatting SASL error and crash reports.</p> <note> - <p>The <c>SASL</c> application in OTP has nothing to do with + <p>The SASL application in OTP has nothing to do with "Simple Authentication and Security Layer" (RFC 4422).</p> </note> </description> @@ -49,7 +49,7 @@ <section> <title>Error Logger Event Handlers</title> <p>The following error logger event handlers are used by - the <c>SASL</c> application.</p> + the SASL application.</p> <taglist> <tag><c>sasl_report_tty_h</c></tag> <item> @@ -57,7 +57,7 @@ reports</em>, and <em>progress reports</em> to <c>stdio</c>. This error logger event handler uses <seealso marker="kernel:kernel_app#error_logger_format_depth">error_logger_format_depth</seealso> - in the <c>Kernel</c> application to limit how much detail is + in the Kernel application to limit how much detail is printed in crash and supervisor reports.</p> </item> <tag><c>sasl_report_file_h</c></tag> @@ -66,7 +66,7 @@ report</em>, and <em>progress report</em> to a single file. This error logger event handler uses <seealso marker="kernel:kernel_app#error_logger_format_depth">error_logger_format_depth</seealso> - in the <c>Kernel</c> application to limit the details + in the Kernel application to limit the details printed in crash and supervisor reports.</p> </item> <tag><c>log_mf_h</c></tag> @@ -75,9 +75,9 @@ error logger to disk. Multiple files and log rotation are used. For efficiency reasons, each event is written as a binary. For more information about this handler, - see <seealso marker="stdlib:log_mf_h">the <c>STDLIB</c> Reference + see <seealso marker="stdlib:log_mf_h">the STDLIB Reference Manual</seealso>.</p> - <p>To activate this event handler, three <c>SASL</c> + <p>To activate this event handler, three SASL configuration parameters must be set, <c>error_logger_mf_dir</c>, <c>error_logger_mf_maxbytes</c>, and <c>error_logger_mf_maxfiles</c>. The next section provides @@ -88,9 +88,9 @@ <section> <title>Configuration</title> - <p>The following configuration parameters are defined for the <c>SASL</c> + <p>The following configuration parameters are defined for the SASL application. For more information about configuration parameters, see - <seealso marker="kernel:app"><c>app(4)</c></seealso> in <c>Kernel</c>.</p> + <seealso marker="kernel:app"><c>app(4)</c></seealso> in Kernel.</p> <p>All configuration parameters are optional.</p> <taglist> <tag><c><![CDATA[sasl_error_logger = Value ]]></c></tag> @@ -112,7 +112,7 @@ Use <c>[append]</c> to have the <c>FileName</c> open in append mode. <c>FileName</c> is a string.</p></item> <tag><c>false</c></tag> - <item><p>No <c>SASL</c> error logger handler is installed.</p></item> + <item><p>No SASL error logger handler is installed.</p></item> </taglist> </item> <tag><c><![CDATA[errlog_type = error | progress | all ]]></c></tag> diff --git a/lib/sasl/doc/src/sasl_intro.xml b/lib/sasl/doc/src/sasl_intro.xml index b71dafb192..f74a7c1db8 100644 --- a/lib/sasl/doc/src/sasl_intro.xml +++ b/lib/sasl/doc/src/sasl_intro.xml @@ -32,7 +32,7 @@ <section> <title>Scope</title> - <p>The <c>SASL</c> application provides support for:</p> + <p>The SASL application provides support for:</p> <list type="bulleted"> <item>Error logging</item> <item>Alarm handling</item> diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml index 7446762de4..fa503fa573 100644 --- a/lib/sasl/doc/src/systools.xml +++ b/lib/sasl/doc/src/systools.xml @@ -85,7 +85,7 @@ </item> <item> <p>If the emulator needs to be restarted after upgrading or - downgrading, that is, if the <c>ERTS</c> version differs + downgrading, that is, if the ERTS version differs between <c>Name.rel</c> and <c>Name2.rel</c></p> </item> </list> @@ -201,10 +201,10 @@ between the applications. Where there are no dependencies, the order in the <c>.rel</c> file is kept.</p> <p>The function fails if the mandatory - applications <c>Kernel</c> and <c>STDLIB</c> are not + applications Kernel and STDLIB are not included in the <c>.rel</c> file and have start type <c>permanent</c> (which is default).</p> - <p>If <c>SASL</c> is not included as an application in + <p>If SASL is not included as an application in the <c>.rel</c> file, a warning is issued because such a release cannot be used in an upgrade. To turn off this warning, add option <c>no_warn_sasl</c>.</p> diff --git a/lib/sasl/src/sasl.app.src b/lib/sasl/src/sasl.app.src index 4ee8a7d6c8..633cdfa070 100644 --- a/lib/sasl/src/sasl.app.src +++ b/lib/sasl/src/sasl.app.src @@ -46,5 +46,5 @@ {errlog_type, all}]}, {mod, {sasl, []}}, {runtime_dependencies, ["tools-2.6.14","stdlib-3.0","kernel-5.0", - "erts-8.0"]}]}. + "erts-8.1"]}]}. diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl index 352e4984df..efe6cc9eb4 100644 --- a/lib/sasl/src/systools_make.erl +++ b/lib/sasl/src/systools_make.erl @@ -1491,8 +1491,9 @@ mandatory_modules() -> preloaded() -> %% Sorted [erl_prim_loader,erl_tracer,erlang, - erts_code_purger, - erts_internal,init,otp_ring0,prim_eval,prim_file, + erts_code_purger,erts_dirty_process_code_checker, + erts_internal,erts_literal_area_collector, + init,otp_ring0,prim_eval,prim_file, prim_inet,prim_zip,zlib]. %%______________________________________________________________________ diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index ad58bb6b7b..10d2539b7f 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -1063,6 +1063,12 @@ otp_9395_check_and_purge(cleanup,_Conf) -> %% OTP-9395 - performance problems when there are MANY processes %% Upgrade which updates many modules (brutal_purge) otp_9395_update_many_mods(Conf) when is_list(Conf) -> + + %% "nain" is very slow - it fails this test quite often due to a + %% long sys call + %% /proc/cpuinfo: "clock: 1249MHz" + inet:gethostname() == {ok,"nain"} andalso throw({skip,"slow test host"}), + %% Set some paths PrivDir = priv_dir(Conf), Dir = filename:join(PrivDir,"otp_9395_update_many_mods"), @@ -1162,6 +1168,12 @@ otp_9395_update_many_mods(cleanup,_Conf) -> %% OTP-9395 - performance problems when there are MANY processes %% Upgrade which removes many modules (brutal_purge) otp_9395_rm_many_mods(Conf) when is_list(Conf) -> + + %% "nain" is very slow - it fails this test quite often due to a + %% long sys call + %% /proc/cpuinfo: "clock: 1249MHz" + inet:gethostname() == {ok,"nain"} andalso throw({skip,"slow test host"}), + %% Set some paths PrivDir = priv_dir(Conf), Dir = filename:join(PrivDir,"otp_9395_rm_many_mods"), @@ -1920,7 +1932,7 @@ wait_nodes_up(Nodes, Tag) -> wait_nodes_up(Nodes0, Tag, Apps) -> ?t:format("wait_nodes_up(~p, ~p, ~p):",[Nodes0, Tag, Apps]), Nodes = fix_nodes(Nodes0), - wait_nodes_up(Nodes, Tag, lists:umerge(Apps,[kernel,stdlib,sasl]), 30). + wait_nodes_up(Nodes, Tag, lists:umerge(Apps,[kernel,stdlib,sasl]), 60). fix_nodes([{Node,InitPid}|Nodes]) -> [{Node,InitPid} | fix_nodes(Nodes)]; @@ -1962,7 +1974,7 @@ wait_nodes_up(Nodes, Tag, Apps, N) -> ?t:format("",[]), ok; _ -> - timer:sleep(1000), + timer:sleep(2000), wait_nodes_up(Pang, Tag, Apps, N-1) end. diff --git a/lib/sasl/test/rh_test_lib.erl b/lib/sasl/test/rh_test_lib.erl index 11935496d8..dacd8b6b9f 100644 --- a/lib/sasl/test/rh_test_lib.erl +++ b/lib/sasl/test/rh_test_lib.erl @@ -18,7 +18,7 @@ cmd(Cmd,Args,Env) -> case open_port({spawn_executable, Cmd}, [{args,Args},{env,Env}]) of Port when is_port(Port) -> unlink(Port), - erlang:port_close(Port), + catch erlang:port_close(Port), % migth already be closed, so catching ok; Error -> Error diff --git a/lib/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl index 94325a8ed6..fc5116dac9 100644 --- a/lib/snmp/src/agent/snmpa_conf.erl +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -154,7 +154,7 @@ do_write_agent_conf(Fd, {intAgentMaxPacketSize = Tag, Val} ) -> do_write_agent_conf(Fd, {snmpEngineMaxMessageSize = Tag, Val} ) -> io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); do_write_agent_conf(Fd, {snmpEngineID = Tag, Val} ) -> - io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); + io:format(Fd, "{~w, ~p}.~n", [Tag, Val]); do_write_agent_conf(_Fd, Crap) -> error({bad_agent_config, Crap}). @@ -758,9 +758,9 @@ do_write_usm_conf( PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}) -> io:format(Fd, "{", []), - io:format(Fd, "\"~s\", ", [EngineID]), - io:format(Fd, "\"~s\", ", [UserName]), - io:format(Fd, "\"~s\", ", [SecName]), + io:format(Fd, "~p, ", [EngineID]), + io:format(Fd, "~p, ", [UserName]), + io:format(Fd, "~p, ", [SecName]), io:format(Fd, "~w, ", [Clone]), io:format(Fd, "~w, ", [AuthP]), do_write_usm2(Fd, AuthKeyC, ", "), @@ -859,15 +859,15 @@ do_write_vacm_conf( {vacmSecurityToGroup, SecModel, SecName, GroupName}) -> io:format( - Fd, "{vacmSecurityToGroup, ~w, \"~s\", \"~s\"}.~n", + Fd, "{vacmSecurityToGroup, ~w, ~p, ~p}.~n", [SecModel, SecName, GroupName]); do_write_vacm_conf( Fd, {vacmAccess, GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}) -> io:format( - Fd, "{vacmAccess, \"~s\", \"~s\", ~w, ~w, ~w, " - "\"~s\", \"~s\", \"~s\"}.~n", + Fd, "{vacmAccess, ~p, ~p, ~w, ~w, ~w, " + "~p, ~p, ~p}.~n", [GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV]); do_write_vacm_conf( @@ -875,7 +875,7 @@ do_write_vacm_conf( {vacmViewTreeFamily, ViewIndex, ViewSubtree, ViewStatus, ViewMask}) -> io:format( - Fd, "{vacmViewTreeFamily, \"~s\", ~w, ~w, ~w}.~n", + Fd, "{vacmViewTreeFamily, ~p, ~w, ~w, ~w}.~n", [ViewIndex, ViewSubtree, ViewStatus, ViewMask]); do_write_vacm_conf(_Fd, Crap) -> error({bad_vacm_config, Crap}). diff --git a/lib/snmp/src/app/snmp.app.src b/lib/snmp/src/app/snmp.app.src index b593e9ea84..d25f66f44a 100644 --- a/lib/snmp/src/app/snmp.app.src +++ b/lib/snmp/src/app/snmp.app.src @@ -23,12 +23,12 @@ {vsn, "%VSN%"}, {modules, [ %% Compiler modules (not in the runtime part of the app) -% snmpc, -% snmpc_lib, -% snmpc_mib_gram, -% snmpc_mib_to_hrl, -% snmpc_misc, -% snmpc_tok, + snmpc, + snmpc_lib, + snmpc_mib_gram, + snmpc_mib_to_hrl, + snmpc_misc, + snmpc_tok, %% Application modules snmp, diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl index db1f9ee61b..d86692aaf6 100644 --- a/lib/snmp/src/compile/snmpc.erl +++ b/lib/snmp/src/compile/snmpc.erl @@ -64,7 +64,7 @@ compile(Input, _Output, Options) -> {ok, _} -> ok; {error, Reason} -> - io:format("~p", [Reason]), + io:format("~tp", [Reason]), error end. @@ -126,7 +126,14 @@ compile(FileName) -> %%---------------------------------------------------------------------- compile(FileName, Options) when is_list(FileName) -> - true = snmpc_misc:is_string(FileName), + case snmpc_misc:check_file(FileName) of + true -> + compile_1(FileName, Options); + false -> + {error, {invalid_file, FileName}} + end. + +compile_1(FileName, Options) -> DefOpts = [{deprecated, true}, {group_check, true}, {i, ["./"]}, diff --git a/lib/snmp/src/compile/snmpc_misc.erl b/lib/snmp/src/compile/snmpc_misc.erl index 933d629746..312074f2e7 100644 --- a/lib/snmp/src/compile/snmpc_misc.erl +++ b/lib/snmp/src/compile/snmpc_misc.erl @@ -29,7 +29,7 @@ bits_to_int/2, ensure_trailing_dir_delimiter/1, foreach/3, - is_string/1, + check_file/1, read_mib/1, read_noexit/2, strip_extension_from_filename/2, @@ -86,21 +86,21 @@ to_upper([C|Cs]) -> [C|to_upper(Cs)]; to_upper([]) -> []. -is_string([]) -> true; -is_string([Tkn | Str]) - when is_integer(Tkn) andalso (Tkn >= 0) andalso (Tkn =< 255) -> - is_string(Str); -is_string(_) -> false. - - +check_file(FileName) -> + case filename:extension(FileName) of + ".mib" -> + filelib:is_regular(FileName); + _ -> + filelib:is_regular(FileName ++ ".mib") + end. + + foreach(Function, ExtraArgs, [H | T]) -> apply(Function, [H | ExtraArgs]), foreach(Function, ExtraArgs, T); foreach(_Function, _ExtraArgs, []) -> true. - - %%---------------------------------------------------------------------- %% Returns: {ok, Mib}|{error, Reason} %% The reason for having the function if this module is: diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk index 87539f88f7..0f54e67c65 100644 --- a/lib/snmp/test/modules.mk +++ b/lib/snmp/test/modules.mk @@ -31,6 +31,7 @@ SUITE_MODULES = \ snmp_agent_mibs_test \ snmp_agent_nfilter_test \ snmp_agent_test \ + snmp_agent_conf_test \ snmp_agent_test_lib \ snmp_manager_config_test \ snmp_manager_user \ diff --git a/lib/snmp/test/snmp_SUITE.erl b/lib/snmp/test/snmp_SUITE.erl index 3b9219739b..05bd86253b 100644 --- a/lib/snmp/test/snmp_SUITE.erl +++ b/lib/snmp/test/snmp_SUITE.erl @@ -81,7 +81,8 @@ groups() -> {group, note_store_test}]}, {agent, [], [{group, mibs_test}, {group, nfilter_test}, - {group, agent_test}, + {group, agent_test}, + {group, agent_conf_test}, {group, snmpnet_test}]}, {manager, [], [{group, manager_config_test}, {group, manager_user_test}, @@ -97,6 +98,7 @@ groups() -> {mibs_test, [], [{snmp_agent_mibs_test, all}]}, {nfilter_test, [], [{snmp_agent_nfilter_test, all}]}, {agent_test, [], [{snmp_agent_test, all}]}, + {agent_conf_test, [], [{snmp_agent_conf_test, all}]}, {snmpnet_test, [], [{snmp_to_snmpnet_SUITE, all}]}, {manager_config_test, [], [{snmp_manager_config_test, all}]}, {manager_user_test, [], [{snmp_manager_user_test, all}]}, diff --git a/lib/snmp/test/snmp_agent_conf_test.erl b/lib/snmp/test/snmp_agent_conf_test.erl new file mode 100644 index 0000000000..0a22bd47d1 --- /dev/null +++ b/lib/snmp/test/snmp_agent_conf_test.erl @@ -0,0 +1,210 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmp_agent_conf_test). + +%%---------------------------------------------------------------------- +%% Include files +%%---------------------------------------------------------------------- + +%-include_lib("test_server/include/test_server.hrl"). +%-include("snmp_test_lib.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-export([ + all/0, + groups/0, + init_per_suite/1, + end_per_suite/1, + + check_agent/1, + check_usm/1, + check_vacm/1 + ]). + + +all() -> [ + check_agent, + check_usm, + check_vacm + ]. + + +groups() -> + []. + + +init_per_suite(Config) -> + PrivDir = ?config(priv_dir, Config), + PrivSubdir = filename:join(PrivDir, "snmp_agent_conf_test"), + ok = filelib:ensure_dir(filename:join(PrivSubdir, "dummy")), + [{priv_subdir, PrivSubdir} | Config]. + +end_per_suite(_Config) -> + ok. + +%%====================================================================== +%% Test data +%%====================================================================== + +engine_ids() -> [ + "plain eid", + "here\"eid", + "comes\neid", + "trouble\0eid", + binary_to_list(<<"ä¸å›½å¼•æ“Žæ ‡è¯†ç¬¦"/utf8>>) +]. + +snmp_admin_strings() -> [ + "plain string", + "heres\"eid", + "trouble\neid", + binary_to_list(<<"ä¸å›½å¼•æ“Žæ ‡è¯†ç¬¦"/utf8>>) +]. + + +%%====================================================================== +%% Test functions +%%====================================================================== + + +check_agent(Config) -> + Dir = ?config(priv_subdir, Config), + lists:foreach( + fun(EngineId) -> check_agent_by_engineid(Dir, EngineId) end, + engine_ids() + ), + ok. + +check_agent_by_engineid(Dir, EngineId) -> + WEntries = [ + snmpa_conf:agent_entry(intAgentIpAddress, {0,0,0,0}), + snmpa_conf:agent_entry(intAgentUDPPort, 161), + snmpa_conf:agent_entry(snmpEngineMaxMessageSize, 484), + snmpa_conf:agent_entry(snmpEngineID, EngineId) + ], + + ok = snmpa_conf:write_agent_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_agent_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + +%%====================================================================== + +check_usm(Config) -> + Dir = ?config(priv_subdir, Config), + EngineId = hd(engine_ids()), + UserName = hd(snmp_admin_strings()), + SecName = hd(snmp_admin_strings()), + + %% vary engine id + lists:foreach( + fun(EngineId_) -> check_usm_by_params(Dir, EngineId_, UserName, SecName) end, + engine_ids() + ), + + %% vary user name + lists:foreach( + fun(UserName_) -> check_usm_by_params(Dir, EngineId, UserName_, SecName) end, + snmp_admin_strings() + ), + + %% vary sec name + lists:foreach( + fun(SecName_) -> check_usm_by_params(Dir, EngineId, UserName, SecName_) end, + snmp_admin_strings() + ), + + ok. + +check_usm_by_params(Dir, EngineId, UserName, SecName) -> + WEntries = [ + snmpa_conf:usm_entry( + EngineId, + UserName, + SecName, + zeroDotZero, + usmNoAuthProtocol, % authproto + "", "", + usmNoPrivProtocol, % privproto + "", "", "", + [], %AuthKey + []) %PrivKey + ], + + ok = snmpa_conf:write_usm_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_usm_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + +%%====================================================================== + +check_vacm(Config) -> + Dir = ?config(priv_subdir, Config), + + %% vary sec name + lists:foreach( + fun(SecName_) -> check_vacm_by_params(Dir, SecName_) end, + snmp_admin_strings() + ), + + ok. + + +check_vacm_by_params(Dir, SecName) -> + WEntries = [ + %% SecModel, SecName, GroupName + snmpa_conf:vacm_s2g_entry(usm, SecName, SecName), + %% GroupName,Prefix,SecModel,SecLevel,Match,ReadView,WriteView,NotifyView + snmpa_conf:vacm_acc_entry(SecName, "", any, noAuthNoPriv, exact, "all", "all", "all") + ], + + ok = snmpa_conf:write_vacm_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_vacm_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + + + +%%====================================================================== + + +%% additional tests needed: +% check_context() +% check_community() +% check_standard() +% check_target_addr() +% check_target_params() +% check_notify() + + +%%====================================================================== +%% Local utility functions +%%====================================================================== + +is_subset(List1, List2) -> + io:format("Check ~p is subset of ~p\n", [List1, List2]), + sets:is_subset( + sets:from_list(List1), + sets:from_list(List2) + ). diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl index 8ae495bb1b..3c1a6f2afd 100644 --- a/lib/snmp/test/snmp_agent_test.erl +++ b/lib/snmp/test/snmp_agent_test.erl @@ -647,22 +647,22 @@ init_per_group(GroupName, Config) -> snmp_test_lib:init_group_top_dir(GroupName, Config). init_per_group_ipv6(GroupName, Config, Init) -> + {ok, Hostname0} = inet:gethostname(), case ct:require(ipv6_hosts) of ok -> - case gen_udp:open(0, [inet6]) of - {ok, S} -> - ok = gen_udp:close(S), - Init( - snmp_test_lib:init_group_top_dir( - GroupName, - [{ipfamily, inet6}, - {ip, ?LOCALHOST(inet6)} - | lists:keydelete(ip, 1, Config)])); - {error, _} -> - {skip, "Host seems to not support IPv6"} - end; + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> + Init( + snmp_test_lib:init_group_top_dir( + GroupName, + [{ipfamily, inet6}, + {ip, ?LOCALHOST(inet6)} + | lists:keydelete(ip, 1, Config)])); + false -> + {skip, "Host does not support IPV6"} + end; _ -> - {skip, "Host does not support IPV6"} + {skip, "Test config ipv6_hosts is missing"} end. end_per_group(all_tcs, Config) -> diff --git a/lib/snmp/test/snmp_app_test.erl b/lib/snmp/test/snmp_app_test.erl index 6e7e85d3b4..5e69866f9a 100644 --- a/lib/snmp/test/snmp_app_test.erl +++ b/lib/snmp/test/snmp_app_test.erl @@ -23,366 +23,29 @@ %%---------------------------------------------------------------------- -module(snmp_app_test). --export([ - all/0, groups/0, - init_per_group/2, end_per_group/2, - init_per_suite/1, end_per_suite/1, - init_per_testcase/2, end_per_testcase/2, +%% Note: This directive should only be used in test suites. +-compile(export_all). - fields/1, - modules/1, - exportall/1, - app_depend/1, - - start_and_stop_empty/1, - start_and_stop_with_agent/1, - start_and_stop_with_manager/1, - start_and_stop_with_agent_and_manager/1, - start_epmty_and_then_agent_and_manager_and_stop/1, - start_with_agent_and_then_manager_and_stop/1, - start_with_manager_and_then_agent_and_stop/1 - ]). - - --include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). --include("snmp_test_lib.hrl"). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- all() -> - Cases = - [ - fields, - modules, - exportall, - app_depend, - {group, start_and_stop} - ], - Cases. - -groups() -> - [{start_and_stop, [], - [start_and_stop_empty, - start_and_stop_with_agent, - start_and_stop_with_manager, - start_and_stop_with_agent_and_manager, - start_epmty_and_then_agent_and_manager_and_stop, - start_with_agent_and_then_manager_and_stop, - start_with_manager_and_then_agent_and_stop]}]. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - -init_per_suite(Config) when is_list(Config) -> - ?DISPLAY_SUITE_INFO(), - - %% Note that part of this stuff (the suite top dir creation) - %% may already be done (if we run the entire snmp suite). - - PrivDir = ?config(priv_dir, Config), - TopDir = filename:join(PrivDir, app), - case file:make_dir(TopDir) of - ok -> - ok; - {error, eexist} -> - ok; - Error -> - fail({failed_creating_subsuite_top_dir, Error}) - end, - AppFile = - case is_app() of - {ok, File} -> - io:format("File: ~n~p~n", [File]), - snmp:print_version_info(), - File; - {error, Reason} -> - fail(Reason) - end, - [{app_topdir, TopDir}, {app_file, AppFile} | Config]. - - -is_app() -> - is_app(?APPLICATION). - -is_app(App) -> - LibDir = code:lib_dir(App), - File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]), - case file:consult(File) of - {ok, [{application, App, AppFile}]} -> - {ok, AppFile}; - Error -> - {error, {invalid_format, Error}} - end. - -end_per_suite(suite) -> []; -end_per_suite(doc) -> []; -end_per_suite(Config) when is_list(Config) -> - Config. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Test server callbacks -init_per_testcase(_Case, Config) -> - Config. - -end_per_testcase(_Case, Config) -> - Config. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -fields(suite) -> - []; -fields(doc) -> - []; -fields(Config) when is_list(Config) -> - AppFile = key1search(app_file, Config), - Fields = [vsn, description, modules, registered, applications], - case check_fields(Fields, AppFile, []) of - [] -> - ok; - Missing -> - fail({missing_fields, Missing}) - end. - -check_fields([], _AppFile, Missing) -> - Missing; -check_fields([Field|Fields], AppFile, Missing) -> - check_fields(Fields, AppFile, check_field(Field, AppFile, Missing)). - -check_field(Name, AppFile, Missing) -> - io:format("checking field: ~p~n", [Name]), - case lists:keymember(Name, 1, AppFile) of - true -> - Missing; - false -> - [Name|Missing] - end. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -modules(suite) -> - []; -modules(doc) -> - []; -modules(Config) when is_list(Config) -> - AppFile = key1search(app_file, Config), - Mods = key1search(modules, AppFile), - EbinList = get_ebin_mods(snmp), - case missing_modules(Mods, EbinList, []) of - [] -> - ok; - Missing -> - fail({missing_modules, Missing}) - end, - Allowed = [snmpc, - snmpc_lib, - snmpc_misc, - snmpc_mib_gram, - snmpc_mib_to_hrl, - snmpc_tok], - case extra_modules(Mods, EbinList, Allowed, []) of - [] -> - ok; - Extra -> - fail({extra_modules, Extra}) - end, - {ok, Mods}. - -get_ebin_mods(App) -> - LibDir = code:lib_dir(App), - EbinDir = filename:join([LibDir,"ebin"]), - {ok, Files0} = file:list_dir(EbinDir), - Files1 = [lists:reverse(File) || File <- Files0], - [list_to_atom(lists:reverse(Name)) || [$m,$a,$e,$b,$.|Name] <- Files1]. - - -missing_modules([], _Ebins, Missing) -> - Missing; -missing_modules([Mod|Mods], Ebins, Missing) -> - case lists:member(Mod, Ebins) of - true -> - missing_modules(Mods, Ebins, Missing); - false -> - io:format("missing module: ~p~n", [Mod]), - missing_modules(Mods, Ebins, [Mod|Missing]) - end. - - -extra_modules(_Mods, [], Allowed, Extra) -> - Extra--Allowed; -extra_modules(Mods, [Mod|Ebins], Allowed, Extra) -> - case lists:member(Mod, Mods) of - true -> - extra_modules(Mods, Ebins, Allowed, Extra); - false -> - io:format("superfluous module: ~p~n", [Mod]), - extra_modules(Mods, Ebins, Allowed, [Mod|Extra]) - end. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -exportall(suite) -> - []; -exportall(doc) -> - []; -exportall(Config) when is_list(Config) -> - AppFile = key1search(app_file, Config), - Mods = key1search(modules, AppFile), - check_export_all(Mods). - - -check_export_all([]) -> - ok; -check_export_all([Mod|Mods]) -> - case (catch apply(Mod, module_info, [compile])) of - {'EXIT', {undef, _}} -> - check_export_all(Mods); - O -> - case lists:keysearch(options, 1, O) of - false -> - check_export_all(Mods); - {value, {options, List}} -> - case lists:member(export_all, List) of - true -> - fail({export_all, Mod}); - false -> - check_export_all(Mods) - end - end - end. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -app_depend(suite) -> - []; -app_depend(doc) -> - []; -app_depend(Config) when is_list(Config) -> - AppFile = key1search(app_file, Config), - Apps = key1search(applications, AppFile), - check_apps(Apps). - - -check_apps([]) -> - ok; -check_apps([App|Apps]) -> - case is_app(App) of - {ok, _} -> - check_apps(Apps); - Error -> - throw({error, {missing_app, {App, Error}}}) - end. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_and_stop_empty(suite) -> - []; -start_and_stop_empty(doc) -> - ["Start and stop the application empty (no configured components)"]; -start_and_stop_empty(Config) when is_list(Config) -> - ?line false = ?IS_SNMP_RUNNING(), - - ?line ok = snmp:start(), - - ?line true = ?IS_SNMP_RUNNING(), - - ?line ok = snmp:stop(), - - ?line false = ?IS_SNMP_RUNNING(), - - ok. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_and_stop_with_agent(suite) -> - []; -start_and_stop_with_agent(doc) -> - ["Start and stop the application with the agent pre-configured"]; -start_and_stop_with_agent(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_and_stop_with_manager(suite) -> - []; -start_and_stop_with_manager(doc) -> - ["Start and stop the application with the manager pre-configured"]; -start_and_stop_with_manager(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_and_stop_with_agent_and_manager(suite) -> - []; -start_and_stop_with_agent_and_manager(doc) -> - ["Start and stop the application with both the agent " - "and the manager pre-configured"]; -start_and_stop_with_agent_and_manager(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_epmty_and_then_agent_and_manager_and_stop(suite) -> - []; -start_epmty_and_then_agent_and_manager_and_stop(doc) -> - ["Start the application empty, then start the agent and then " - "the manager and then stop the application"]; -start_epmty_and_then_agent_and_manager_and_stop(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_with_agent_and_then_manager_and_stop(suite) -> - []; -start_with_agent_and_then_manager_and_stop(doc) -> - ["Start the application with the agent pre-configured, " - "then start the manager and then stop the application"]; -start_with_agent_and_then_manager_and_stop(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_with_manager_and_then_agent_and_stop(suite) -> - []; -start_with_manager_and_then_agent_and_stop(doc) -> - ["Start the application with the manager pre-configured, " - "then start the agent and then stop the application"]; -start_with_manager_and_then_agent_and_stop(Config) when is_list(Config) -> - ?SKIP(not_implemented_yet). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -fail(Reason) -> - exit({suite_failed, Reason}). - -key1search(Key, L) -> - case lists:keysearch(Key, 1, L) of - undefined -> - fail({not_found, Key, L}); - {value, {Key, Value}} -> - Value - end. + [ + app, + appup + ]. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +app() -> + [{doc, "Test that the snmp app file is ok"}]. +app(Config) when is_list(Config) -> + ok = test_server:app_test(snmp). +%%-------------------------------------------------------------------- +appup() -> + [{doc, "Test that the snmp appup file is ok"}]. +appup(Config) when is_list(Config) -> + ok = test_server:appup_test(snmp). diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index d17882e765..71f4017d8b 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -583,38 +583,38 @@ init_per_group(event_tests_mt = GroupName, Config) -> GroupName, [{manager_net_if_module, snmpm_net_if_mt} | Config]); init_per_group(ipv6_mt = GroupName, Config) -> + {ok, Hostname0} = inet:gethostname(), case ct:require(ipv6_hosts) of ok -> - case gen_udp:open(0, [inet6]) of - {ok, S} -> - ok = gen_udp:close(S), + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> ipv6_init( snmp_test_lib:init_group_top_dir( GroupName, [{manager_net_if_module, snmpm_net_if_mt} | Config])); - {error, _} -> - {skip, "Host seems to not support IPv6"} + false -> + {skip, "Host does not support IPv6"} end; _ -> - {skip, "Host does not support IPV6"} + {skip, "Test config ipv6_hosts is missing"} end; init_per_group(ipv6 = GroupName, Config) -> + {ok, Hostname0} = inet:gethostname(), case ct:require(ipv6_hosts) of ok -> - case gen_udp:open(0, [inet6]) of - {ok, S} -> - ok = gen_udp:close(S), + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> ipv6_init(snmp_test_lib:init_group_top_dir(GroupName, Config)); - {error, _} -> - {skip, "Host seems to not support IPv6"} + false -> + {skip, "Host does not support IPv6"} end; _ -> - {skip, "Host does not support IPV6"} + {skip, "Test config ipv6_hosts is missing"} end; init_per_group(GroupName, Config) -> snmp_test_lib:init_group_top_dir(GroupName, Config). - + end_per_group(_GroupName, Config) -> %% Do we really need to do this? lists:keydelete(snmp_group_top_dir, 1, Config). diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl index ac9e37bc8b..24c14d86ea 100644 --- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl +++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl @@ -121,14 +121,14 @@ init_per_group(_, Config) -> Config. init_per_group_ipv6(Families, Config) -> + {ok, Hostname0} = inet:gethostname(), case ct:require(ipv6_hosts) of ok -> - case gen_udp:open(0, [inet6]) of - {ok, S} -> - ok = gen_udp:close(S), + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> init_per_group_ip(Families, Config); - {error, _} -> - {skip, "Host seems to not support IPv6"} + false -> + {skip, "Host does not support IPv6"} end; _ -> {skip, "Test config ipv6_hosts is missing"} diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index ef6ee79654..f9d11b2a60 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -132,6 +132,22 @@ </section> +<section><title>Ssh 4.2.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SSH client does not any longer retry a bad password given + as option to ssh:connect et al.</p> + <p> + Own Id: OTP-13674 Aux Id: TR-HU92273 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.2.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index e6c54d27bf..ef9f7cbd9b 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2004</year><year>2015</year> + <year>2004</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -756,7 +756,7 @@ <p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>, and <c>ssh</c>. Default type is <c>temporary</c>. For more information, see the <seealso marker="kernel:application">application(3)</seealso> - manual page in <c>kernel</c>.</p> + manual page in Kernel.</p> </desc> </func> @@ -769,7 +769,7 @@ <desc> <p>Stops the <c>ssh</c> application. For more information, see the <seealso marker="kernel:application">application(3)</seealso> - manual page in <c>kernel</c>.</p> + manual page in Kernel.</p> </desc> </func> diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml index f6ce44c015..5cc4c24889 100644 --- a/lib/ssh/doc/src/ssh_app.xml +++ b/lib/ssh/doc/src/ssh_app.xml @@ -4,7 +4,7 @@ <appref> <header> <copyright> - <year>2012</year><year>2015</year> + <year>2012</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -203,7 +203,7 @@ <section> <title>Unicode support</title> <p>Unicode filenames are supported if the emulator and the underlaying OS support it. See section DESCRIPTION in the - <seealso marker="kernel:file">file</seealso> manual page in <c>kernel</c> for information about this subject. + <seealso marker="kernel:file">file</seealso> manual page in Kernel for information about this subject. </p> <p>The shell and the cli both support unicode. </p> diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml index 907b0b3bec..7b598494f7 100644 --- a/lib/ssh/doc/src/ssh_channel.xml +++ b/lib/ssh/doc/src/ssh_channel.xml @@ -139,7 +139,7 @@ enters the <c>ssh_channel</c> process receive loop and become an <c>ssh_channel process</c>. The process must have been started using one of the start functions in <c>proc_lib</c>, see the <seealso - marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in <c>stdlib</c>. + marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in STDLIB. The user is responsible for any initialization of the process and must call <seealso marker = "#init-1">ssh_channel:init/1</seealso>. </p> diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 3245ba5197..76b7d8cd55 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -45,7 +45,7 @@ "erts-6.0", "kernel-3.0", "public_key-1.1", - "stdlib-3.0" + "stdlib-3.1" ]}]}. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 0570853a9b..1d7be3547b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -52,16 +52,15 @@ %% is temporary. see application(3) %%-------------------------------------------------------------------- start() -> - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssh). + start(temporary). start(Type) -> - application:start(crypto, Type), - application:start(asn1), - application:start(public_key, Type), - application:start(ssh, Type). + case application:ensure_all_started(ssh, Type) of + {ok, _} -> + ok; + Other -> + Other + end. %%-------------------------------------------------------------------- -spec stop() -> ok | {error, term()}. @@ -90,7 +89,7 @@ connect(Socket, Options, Timeout) when is_port(Socket) -> {error, Error}; {_SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of - ok -> + ok -> {ok, {Host,_Port}} = inet:sockname(Socket), Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); @@ -128,23 +127,23 @@ connect(Host, Port, Options, Timeout) -> -spec close(pid()) -> ok. %% %% Description: Closes an ssh connection. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- close(ConnectionRef) -> ssh_connection_handler:stop(ConnectionRef). %%-------------------------------------------------------------------- -spec connection_info(pid(), [atom()]) -> [{atom(), term()}]. %% -%% Description: Retrieves information about a connection. -%%-------------------------------------------------------------------- +%% Description: Retrieves information about a connection. +%%-------------------------------------------------------------------- connection_info(ConnectionRef, Options) -> ssh_connection_handler:connection_info(ConnectionRef, Options). %%-------------------------------------------------------------------- -spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}]. %% -%% Description: Retrieves information about a connection. -%%-------------------------------------------------------------------- +%% Description: Retrieves information about a connection. +%%-------------------------------------------------------------------- channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). @@ -153,9 +152,9 @@ channel_info(ConnectionRef, ChannelId, Options) -> -spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -%% Description: Starts a server listening for SSH connections +%% Description: Starts a server listening for SSH connections %% on the given port. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- daemon(Port) -> daemon(Port, []). @@ -188,9 +187,9 @@ daemon_info(Pid) -> -spec stop_listener(pid()) -> ok. -spec stop_listener(inet:ip_address(), integer()) -> ok. %% -%% Description: Stops the listener, but leaves +%% Description: Stops the listener, but leaves %% existing connections started by the listener up and running. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- stop_listener(SysSup) -> ssh_system_sup:stop_listener(SysSup). stop_listener(Address, Port) -> @@ -202,9 +201,9 @@ stop_listener(Address, Port, Profile) -> -spec stop_daemon(pid()) -> ok. -spec stop_daemon(inet:ip_address(), integer()) -> ok. %% -%% Description: Stops the listener and all connections started by +%% Description: Stops the listener and all connections started by %% the listener. -%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- stop_daemon(SysSup) -> ssh_system_sup:stop_system(SysSup). stop_daemon(Address, Port) -> @@ -243,7 +242,7 @@ start_shell({ok, ConnectionRef}) -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), - Args = [{channel_cb, ssh_shell}, + Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], {ok, State} = ssh_channel:init([Args]), @@ -256,7 +255,7 @@ start_shell(Error) -> %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- -default_algorithms() -> +default_algorithms() -> ssh_transport:default_algorithms(). %%-------------------------------------------------------------------- @@ -296,13 +295,13 @@ daemon_shell_opt(Options) -> daemon_host_inet_opt(HostAddr, Options1) -> case HostAddr of any -> - {ok, Host0} = inet:gethostname(), + {ok, Host0} = inet:gethostname(), {Host0, proplists:get_value(inet, Options1, inet), Options1}; {_,_,_,_} -> - {HostAddr, inet, + {HostAddr, inet, [{ip, HostAddr} | Options1]}; {_,_,_,_,_,_,_,_} -> - {HostAddr, inet6, + {HostAddr, inet6, [{ip, HostAddr} | Options1]} end. @@ -313,8 +312,8 @@ start_daemon(Socket, Options) -> {error, Error}; {SocketOptions, SshOptions} -> case valid_socket_to_use(Socket, Options) of - ok -> - try + ok -> + try do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) catch throw:bad_fd -> {error,bad_fd}; @@ -330,16 +329,16 @@ start_daemon(Host, Port, Options, Inet) -> {error, _Reason} = Error -> Error; {SocketOptions, SshOptions}-> - try + try do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. - + do_start_daemon(Socket, SshOptions, SocketOptions) -> - {ok, {IP,Port}} = + {ok, {IP,Port}} = try {ok,_} = inet:sockname(Socket) catch _:_ -> throw(bad_socket) @@ -351,7 +350,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> {address, Host}, {port, Port}, {role, server}, - {socket_opts, SocketOptions}, + {socket_opts, SocketOptions}, {ssh_opts, SshOptions}], {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), case ssh_system_sup:system_supervisor(Host, Port, Profile) of @@ -385,7 +384,7 @@ do_start_daemon(Socket, SshOptions, SocketOptions) -> end. do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> - {Host,Port1} = + {Host,Port1} = try case proplists:get_value(fd, SocketOptions) of undefined -> @@ -402,21 +401,21 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> {Port, WaitRequestControl, Opts0} = case Port1 of 0 -> %% Allocate the socket here to get the port number... - {_, Callback, _} = + {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions), {ok,{_,LPort}} = inet:sockname(LSock), - {LPort, - {LSock,Callback}, + {LPort, + {LSock,Callback}, [{lsocket,LSock},{lsock_owner,self()}] }; _ -> {Port1, false, []} end, - Opts = [{address, Host}, + Opts = [{address, Host}, {port, Port}, {role, server}, - {socket_opts, SocketOptions}, + {socket_opts, SocketOptions}, {ssh_opts, SshOptions} | Opts0], case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> @@ -465,7 +464,7 @@ find_hostport(Fd) -> {ok, HostPort} = inet:sockname(S), ok = inet:close(S), HostPort. - + handle_options(Opts) -> try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of @@ -480,9 +479,9 @@ handle_options(Opts) -> algs_compatibility(Os0) -> %% Take care of old options 'public_key_alg' and 'pref_public_key_algs' case proplists:get_value(public_key_alg, Os0) of - undefined -> + undefined -> Os0; - A when is_atom(A) -> + A when is_atom(A) -> %% Skip public_key_alg if pref_public_key_algs is defined: Os = lists:keydelete(public_key_alg, 1, Os0), case proplists:get_value(pref_public_key_algs,Os) of @@ -492,7 +491,7 @@ algs_compatibility(Os0) -> [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os]; undefined -> throw({error, {eoptions, {public_key_alg,A} }}); - _ -> + _ -> Os end; V -> @@ -620,7 +619,7 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) - Opt; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; -handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> +handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> handle_pref_algs(Opt); handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> @@ -629,7 +628,7 @@ handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> lists:foldl( fun({N,G,P}, Acc) when is_integer(N),N>0, is_integer(G),G>0, - is_integer(P),P>0 -> + is_integer(P),P>0 -> [{N,{G,P}} | Acc]; ({N,{G,P}}, Acc) when is_integer(N),N>0, is_integer(G),G>0, @@ -637,7 +636,7 @@ handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) -> [{N,{G,P}} | Acc]; ({N,GPs}, Acc) when is_list(GPs) -> lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, - is_integer(Pi),Pi>0 -> + is_integer(Pi),Pi>0 -> [{N,{Gi,Pi}} | Acci] end, Acc, GPs) end, [], L0))}; @@ -647,7 +646,7 @@ handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, Tag == ssh_moduli_file -> {ok,GroupDefs} = case Tag of - file -> + file -> file:consult(File); ssh_moduli_file -> case file:open(File,[read]) of @@ -672,14 +671,14 @@ handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0, catch _:_ -> throw({error, {{eoptions, Opt}, "Bad format in file: "++File}}) - end; - + end; + -handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, +handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0, is_integer(Max), Max>=Min -> %% Server Opt; -handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, +handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, is_integer(I), I>=Min, is_integer(Max), Max>=I -> %% Client @@ -724,7 +723,7 @@ handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3 Opt; handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> Opt; -handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), +handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module), is_atom(Function) -> Opt; handle_ssh_option({exec, Function} = Opt) when is_function(Function) -> @@ -772,7 +771,7 @@ handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; -handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> +handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> Opt; handle_ssh_option({id_string, random}) -> {id_string, {random,2,5}}; %% 2 - 5 random characters @@ -814,11 +813,11 @@ handle_pref_algs({preferred_algorithms,Algs}) -> of DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs) catch - _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, + _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}}, "Bad preferred_algorithms key"}}) end || {Key,Vals} <- Algs] }; - + Dups -> throw({error, {{eoptions, {preferred_algorithms,Dups}}, "Duplicates found"}}) catch @@ -857,13 +856,13 @@ handle_pref_alg(Key, ) -> handle_pref_alg(Key, lists:reverse(Vs), Sup); -handle_pref_alg(Key, +handle_pref_alg(Key, Vs=[V|_], Sup=[{client2server,_},{server2client,_}] ) when is_atom(V) -> handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup); -handle_pref_alg(Key, +handle_pref_alg(Key, Vs=[V|_], Sup=[S|_] ) when is_atom(V), is_atom(S) -> @@ -878,14 +877,14 @@ chk_alg_vs(OptKey, Values, SupportedValues) -> [] -> Values; Bad -> throw({error, {{eoptions, {OptKey,Bad}}, "Unsupported value(s) found"}}) end. - + handle_ip(Inet) -> %% Default to ipv4 case lists:member(inet, Inet) of true -> Inet; false -> case lists:member(inet6, Inet) of - true -> + true -> Inet; false -> [inet | Inet] @@ -916,8 +915,8 @@ directory_exist_readable(Dir) -> {error, Error} -> {error, Error} end. - - + + collect_per_size(L) -> lists:foldr( @@ -948,7 +947,7 @@ read_moduli_file(D, I, Acc) -> read_moduli_file(D, I+1, Acc) end end. - + handle_user_pref_pubkey_algs([], Acc) -> {true, lists:reverse(Acc)}; handle_user_pref_pubkey_algs([H|T], Acc) -> @@ -963,7 +962,7 @@ handle_user_pref_pubkey_algs([H|T], Acc) -> false end. -fmt_host({A,B,C,D}) -> +fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]); -fmt_host(T={_,_,_,_,_,_,_,_}) -> +fmt_host(T={_,_,_,_,_,_,_,_}) -> lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 868f3a9181..4cd91177f6 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -127,7 +127,7 @@ recv_sequence = 0, keyex_key, keyex_info, - random_length_padding = 255, % From RFC 4253 section 6. + random_length_padding = 15, % From RFC 4253 section 6. %% User auth user, diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index fb5e086656..ac35b70209 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -140,7 +140,7 @@ publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = sha, %% Maybe option?! + Hash = ssh_transport:sha(Alg), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), case KeyCb:user_key(Alg, Opts) of {ok, PrivKey} -> @@ -260,32 +260,54 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "publickey", - data = Data}, - SessionId, + data = <<?BYTE(?FALSE), + ?UINT32(ALen), BAlg:ALen/binary, + ?UINT32(KLen), KeyBlob:KLen/binary, + _/binary + >> + }, + _SessionId, #ssh{opts = Opts, userauth_supported_methods = Methods} = Ssh) -> - <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary, - ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data, - Alg = binary_to_list(BAlg), - case HaveSig of - ?TRUE -> - case verify_sig(SessionId, User, "ssh-connection", Alg, - KeyBlob, SigWLen, Opts) of - true -> - {authorized, User, - ssh_transport:ssh_packet( - #ssh_msg_userauth_success{}, Ssh)}; - false -> - {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)} - end; - ?FALSE -> + + case pre_verify_sig(User, binary_to_list(BAlg), + KeyBlob, Opts) of + true -> {not_authorized, {User, undefined}, ssh_transport:ssh_packet( - #ssh_msg_userauth_pk_ok{algorithm_name = Alg, - key_blob = KeyBlob}, Ssh)} + #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), + key_blob = KeyBlob}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} + end; + +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "publickey", + data = <<?BYTE(?TRUE), + ?UINT32(ALen), BAlg:ALen/binary, + ?UINT32(KLen), KeyBlob:KLen/binary, + SigWLen/binary>> + }, + SessionId, + #ssh{opts = Opts, + userauth_supported_methods = Methods} = Ssh) -> + + case verify_sig(SessionId, User, "ssh-connection", + binary_to_list(BAlg), + KeyBlob, SigWLen, Opts) of + true -> + {authorized, User, + ssh_transport:ssh_packet( + #ssh_msg_userauth_success{}, Ssh)}; + false -> + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = Methods, + partial_success = false}, Ssh)} end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -384,10 +406,22 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, kb_tries_left = KbTriesLeft, user = User, userauth_supported_methods = Methods} = Ssh) -> + SendOneEmpty = proplists:get_value(tstflg, Opts) == one_empty, case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of + {true,Ssh1} when SendOneEmpty==true -> + Msg = #ssh_msg_userauth_info_request{name = "", + instruction = "", + language_tag = "", + num_prompts = 0, + data = <<?BOOLEAN(?FALSE)>> + }, + {authorized_but_one_more, User, + ssh_transport:ssh_packet(Msg, Ssh1)}; + {true,Ssh1} -> {authorized, User, ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)}; + {false,Ssh1} -> {not_authorized, {User, {error,"Bad user or password"}}, ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ @@ -397,6 +431,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, )} end; +handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}}, + #ssh{user = User} = Ssh) -> + {authorized, User, + ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; + handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> ssh_connection_handler:disconnect( @@ -473,19 +512,34 @@ get_password_option(Opts, User) -> false -> proplists:get_value(password, Opts, false) end. -verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> - {ok, Key} = decode_public_key_v2(KeyBlob, Alg), - KeyCb = proplists:get_value(key_cb, Opts, ssh_file), +pre_verify_sig(User, Alg, KeyBlob, Opts) -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + KeyCb:is_auth_key(Key, User, Opts) + catch + _:_ -> + false + end. - case KeyCb:is_auth_key(Key, User, Opts) of - true -> - PlainText = build_sig_data(SessionId, User, - Service, KeyBlob, Alg), - <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen, - <<?UINT32(AlgLen), _Alg:AlgLen/binary, - ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig, - ssh_transport:verify(PlainText, sha, Sig, Key); - false -> +verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> + try + {ok, Key} = decode_public_key_v2(KeyBlob, Alg), + KeyCb = proplists:get_value(key_cb, Opts, ssh_file), + + case KeyCb:is_auth_key(Key, User, Opts) of + true -> + PlainText = build_sig_data(SessionId, User, + Service, KeyBlob, Alg), + <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen, + <<?UINT32(AlgLen), _Alg:AlgLen/binary, + ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig, + ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key); + false -> + false + end + catch + _:_ -> false end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index f9f4c82351..facf6b561a 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,7 +60,8 @@ ]). %%% Behaviour callbacks --export([handle_event/4, terminate/3, format_status/2, code_change/4]). +-export([callback_mode/0, handle_event/4, terminate/3, + format_status/2, code_change/4]). %%% Exports not intended to be used :). They are used for spawning and tests -export([init_connection_handler/3, % proc_lib:spawn needs this @@ -338,6 +339,7 @@ renegotiate_data(ConnectionHandler) -> ssh_params :: #ssh{} | undefined, socket :: inet:socket(), + sender :: pid() | undefined, decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), undecrypted_packet_length :: undefined | non_neg_integer(), @@ -366,22 +368,21 @@ init_connection_handler(Role, Socket, Opts) -> {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag + sender = spawn_link(fun() -> nonblocking_sender(Socket, Callback) end), + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag } of S -> gen_statem:enter_loop(?MODULE, [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, {hello,Role}, S) catch _:Error -> gen_statem:enter_loop(?MODULE, [], - handle_event_function, {init_error,Error}, S0) end. @@ -504,6 +505,9 @@ init_ssh_record(Role, Socket, Opts) -> %%% ######## Error in the initialisation #### +callback_mode() -> + handle_event_function. + handle_event(_, _Event, {init_error,Error}, _) -> case Error of {badmatch,{error,enotconn}} -> @@ -523,7 +527,7 @@ handle_event(_, _Event, {init_error,Error}, _) -> %% The very first event that is sent when the we are set as controlling process of Socket handle_event(_, socket_control, {hello,_}, D) -> VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)), - ok = send_bytes(VsnMsg, D), + send_bytes(VsnMsg, D), case inet:getopts(Socket=D#data.socket, [recbuf]) of {ok, [{recbuf,Size}]} -> %% Set the socket to the hello text line handling mode: @@ -548,7 +552,7 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> server -> %% But the client may NOT send them to the server. Openssh answers with cleartext, %% and so do we - ok = send_bytes("Protocol mismatch.", D), + send_bytes("Protocol mismatch.", D), {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}} end; @@ -563,7 +567,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) -> {active, once}, {recbuf, D#data.inet_initial_recbuf_size}]), {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1), - ok = send_bytes(SshPacket, D), + send_bytes(SshPacket, D), {next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh, key_exchange_init_msg = KeyInitMsg}}; not_supported -> @@ -581,7 +585,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload), Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of {ok, NextKexMsg, Ssh2} when Role==client -> - ok = send_bytes(NextKexMsg, D), + send_bytes(NextKexMsg, D), Ssh2; {ok, Ssh2} when Role==server -> Ssh2 @@ -594,43 +598,43 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg}, %%%---- diffie-hellman handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexdhReply, D), + send_bytes(KexdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- diffie-hellman group exchange handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params), - ok = send_bytes(GexGroup, D), + send_bytes(GexGroup, D), {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params), - ok = send_bytes(KexGexInit, D), + send_bytes(KexGexInit, D), {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}}; %%%---- elliptic curve diffie-hellman handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) -> {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params), - ok = send_bytes(KexEcdhReply, D), + send_bytes(KexEcdhReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) -> {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}}; @@ -638,9 +642,9 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) -> {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params), - ok = send_bytes(KexGexReply, D), + send_bytes(KexGexReply, D), {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}}; @@ -648,7 +652,7 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) -> {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params), - ok = send_bytes(NewKeys, D), + send_bytes(NewKeys, D), {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}}; @@ -660,7 +664,7 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> Ssh = case Role of client -> {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1), - ok = send_bytes(MsgReq, D), + send_bytes(MsgReq, D), Ssh2; server -> Ssh1 @@ -678,7 +682,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; _ -> @@ -690,7 +694,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - ok = send_bytes(Msg, State), + send_bytes(Msg, State), {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; @@ -707,7 +711,7 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}}; {"ssh-connection", "ssh-connection", Method} -> @@ -717,7 +721,7 @@ handle_event(_, %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of {authorized, User, {Reply, Ssh}} -> - ok = send_bytes(Reply, D), + send_bytes(Reply, D), D#data.starter ! ssh_connected, connected_fun(User, Method, D), {next_state, {connected,server}, @@ -725,11 +729,11 @@ handle_event(_, ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), - ok = send_bytes(Reply, D), + send_bytes(Reply, D), {keep_state, D#data{ssh_params = Ssh}} end; false -> @@ -818,9 +822,21 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D), send_bytes(Reply, D), - {next_state, {userauth,server}, D#data{ssh_params = Ssh}} + {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; + + {authorized_but_one_more, _User, {Reply, Ssh}} -> + send_bytes(Reply, D), + {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}} end; +handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) -> + {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params), + send_bytes(Reply, D), + D#data.starter ! ssh_connected, + connected_fun(User, "keyboard-interactive", D), + {next_state, {connected,server}, D#data{auth_user = User, + ssh_params = Ssh#ssh{authenticated = true}}}; + handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D0) -> Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference, @@ -1234,9 +1250,12 @@ handle_event(internal, prepare_next_packet, _, D) -> handle_event(info, {CloseTag,Socket}, StateName, D = #data{socket = Socket, transport_close_tag = CloseTag}) -> - disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, - description = "Connection closed"}, - StateName, D); + %% Simulate a disconnect from the peer + handle_event(info, + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, + description = "Connection closed"}, + StateName, + D); handle_event(info, {timeout, {_, From} = Request}, _, #data{connection_state = #connection{requests = Requests} = C0} = D) -> @@ -1401,12 +1420,12 @@ fmt_stat_rec(FieldNames, Rec, Exclude) -> state_name(), #data{}, term() - ) -> {gen_statem:callback_mode(), state_name(), #data{}}. + ) -> {ok, state_name(), #data{}}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . code_change(_OldVsn, StateName, State, _Extra) -> - {handle_event_function, StateName, State}. + {ok, StateName, State}. %%==================================================================== @@ -1428,18 +1447,15 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %% Stopping -type finalize_termination_result() :: ok . -finalize_termination(_StateName, #data{transport_cb = Transport, - connection_state = Connection, - socket = Socket}) -> - case Connection of +finalize_termination(_StateName, D) -> + case D#data.connection_state of #connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); _ -> do_nothing end, - (catch Transport:close(Socket)), - ok. + close_transport(D). %%-------------------------------------------------------------------- %% "Invert" the Role @@ -1494,8 +1510,33 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. -send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> - Transport:send(Socket, Bytes). +send_bytes(Bytes, #data{sender = Sender}) -> + Sender ! {send,Bytes}, + ok. + +close_transport(D) -> + D#data.sender ! close, + ok. + + +nonblocking_sender(Socket, Callback) -> + receive + {send, Bytes} -> + case Callback:send(Socket, Bytes) of + ok -> + nonblocking_sender(Socket, Callback); + E = {error,_} -> + exit({shutdown,E}) + end; + + close -> + case Callback:close(Socket) of + ok -> + ok; + E = {error,_} -> + exit({shutdown,E}) + end + end. handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7cb3b75ac0..15b80de30a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -46,7 +46,7 @@ handle_kex_ecdh_reply/2, extract_public_key/1, ssh_packet/2, pack/2, - sign/3, verify/4]). + sha/1, sign/3, verify/4]). %%% For test suites -export([pack/3]). @@ -1619,6 +1619,11 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). +sha('ssh-rsa') -> sha; +sha('ssh-dss') -> sha; +sha('ecdsa-sha2-nistp256') -> sha(secp256r1); +sha('ecdsa-sha2-nistp384') -> sha(secp384r1); +sha('ecdsa-sha2-nistp521') -> sha(secp521r1); sha(secp256r1) -> sha256; sha(secp384r1) -> sha384; sha(secp521r1) -> sha512; diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl index 0f68130a05..8b2db0e1a8 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE.erl +++ b/lib/ssh/test/ssh_algorithms_SUITE.erl @@ -180,21 +180,19 @@ simple_exec(Config) -> %%-------------------------------------------------------------------- %% Testing if no group matches simple_exec_groups_no_match_too_small(Config) -> - try simple_exec_group({400,500,600}, Config) - of - _ -> ct:fail("Exec though no group available") - catch - error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} -> - ok - end. + try_exec_simple_group({400,500,600}, Config). simple_exec_groups_no_match_too_large(Config) -> - try simple_exec_group({9200,9500,9700}, Config) + try_exec_simple_group({9200,9500,9700}, Config). + + +try_exec_simple_group(Group, Config) -> + try simple_exec_group(Group, Config) of _ -> ct:fail("Exec though no group available") catch - error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} -> - ok + error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} -> ok; + error:{badmatch,{error,"Connection closed"}} -> ok end. %%-------------------------------------------------------------------- diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 575c1af3a9..212b99c695 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.3.1 +SSH_VSN = 4.3.2 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml index a66e947bc1..f317dfded4 100644 --- a/lib/ssl/doc/src/ssl_app.xml +++ b/lib/ssl/doc/src/ssl_app.xml @@ -141,6 +141,16 @@ marker="ssl#clear_pem_cache-0">ssl:clear_pem_cache/0</seealso> </item> + + <tag><c><![CDATA[bypass_pem_cache = boolean() <optional>]]></c></tag> + <item> + <p>Introduced in ssl-8.0.2. Disables the PEM-cache. + The PEM cache has proven to be a bottleneck, until the + implementation has been improved this can be used as + a workaround. Defaults to false. + </p> + </item> + <tag><c><![CDATA[alert_timeout = integer() <optional>]]></c></tag> <item> <p> diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml index 4bd5f67202..1150043e76 100644 --- a/lib/ssl/doc/src/ssl_distribution.xml +++ b/lib/ssl/doc/src/ssl_distribution.xml @@ -71,8 +71,8 @@ <section> <title>Building Boot Scripts Including the ssl Application</title> <p>Boot scripts are built using the <c>systools</c> utility in the - <c>sasl</c> application. For more information on <c>systools</c>, - see the <c>sasl</c> documentation. This is only an example of + SASL application. For more information on <c>systools</c>, + see the SASL documentation. This is only an example of what can be done.</p> <p>The simplest boot script possible includes only the Kernel diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index b8be686b99..479f68f4bb 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -42,22 +42,17 @@ -export([next_record/1, next_event/3]). %% Handshake handling --export([%%renegotiate/2, - send_handshake/2, queue_handshake/2, queue_change_cipher/2]). +-export([renegotiate/2, + reinit_handshake_data/1, + send_handshake/2, queue_handshake/2, queue_change_cipher/2, + select_sni_extension/1]). %% Alert and close handling --export([%%send_alert/2, handle_own_alert/4, handle_close_alert/3, - handle_normal_shutdown/3 %%, close/5 - %%alert_user/6, alert_user/9 - ]). +-export([send_alert/2, close/5]). %% Data handling --export([%%write_application_data/3, - read_application_data/2, - passive_receive/2, next_record_if_active/1%, - %%handle_common_event/4, - %handle_packet/3 +-export([passive_receive/2, next_record_if_active/1, handle_common_event/4 ]). %% gen_statem state functions @@ -65,9 +60,7 @@ hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states connection/3]). %% gen_statem callbacks --export([terminate/3, code_change/4, format_status/2]). - --define(GEN_STATEM_CB_MODE, state_functions). +-export([callback_mode/0, terminate/3, code_change/4, format_status/2]). %%==================================================================== %% Internal application API @@ -104,10 +97,11 @@ send_handshake(Handshake, State) -> send_handshake_flight(queue_handshake(Handshake, State)). queue_flight_buffer(Msg, #state{negotiated_version = Version, - connection_states = #connection_states{ - current_write = - #connection_state{epoch = Epoch}}, + connection_states = ConnectionStates, flight_buffer = Flight} = State) -> + ConnectionState = + ssl_record:current_connection_state(ConnectionStates, write), + Epoch = maps:get(epoch, ConnectionState), State#state{flight_buffer = Flight ++ [{Version, Epoch, Msg}]}. queue_handshake(Handshake, #state{negotiated_version = Version, @@ -141,6 +135,25 @@ send_alert(Alert, #state{negotiated_version = Version, Transport:send(Socket, BinMsg), State0#state{connection_states = ConnectionStates}. +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + Transport:close(Socket). + +reinit_handshake_data(#state{protocol_buffers = Buffers} = State) -> + State#state{premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history(), + protocol_buffers = + Buffers#protocol_buffers{dtls_fragment_state = + dtls_handshake:dtls_handshake_new_flight(0)}}. + +select_sni_extension(#client_hello{extensions = HelloExtensions}) -> + HelloExtensions#hello_extensions.sni; +select_sni_extension(_) -> + undefined. + %%==================================================================== %% tls_connection_sup API %%==================================================================== @@ -161,12 +174,15 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + gen_statem:enter_loop(?MODULE, [], init, State) catch throw:Error -> - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error,State0}) + gen_statem:enter_loop(?MODULE, [], error, {Error,State0}) end. +callback_mode() -> + state_functions. + %%-------------------------------------------------------------------- %% State functionsconnection/2 %%-------------------------------------------------------------------- @@ -231,7 +247,7 @@ hello(internal, #client_hello{client_version = ClientVersion, case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State); + ssl_connection:handle_own_alert(Alert, ClientVersion, hello, State); {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> Protocol = case Protocol0 of @@ -255,7 +271,7 @@ hello(internal, #server_hello{} = Hello, ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State); + ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State); {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) @@ -334,7 +350,7 @@ handle_info({Protocol, _, Data}, StateName, {Record, State} -> next_event(StateName, Record, State); #alert{} = Alert -> - handle_normal_shutdown(Alert, StateName, State0), + ssl_connection:handle_normal_shutdown(Alert, StateName, State0), {stop, {shutdown, own_alert}} end; handle_info({CloseTag, Socket}, StateName, @@ -354,7 +370,7 @@ handle_info({CloseTag, Socket}, StateName, %%invalidate_session(Role, Host, Port, Session) ok end, - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), {stop, {shutdown, transport_closed}}; handle_info(Msg, StateName, State) -> ssl_connection:handle_info(Msg, StateName, State). @@ -362,6 +378,51 @@ handle_info(Msg, StateName, State) -> handle_call(Event, From, StateName, State) -> ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). +handle_common_event(internal, #alert{} = Alert, StateName, + #state{negotiated_version = Version} = State) -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State); + +%%% DTLS record protocol level handshake messages +handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE} = Record, + StateName, + #state{protocol_buffers = + #protocol_buffers{dtls_packets = Packets0, + dtls_fragment_state = HsState0} = Buffers, + negotiated_version = Version} = State0) -> + try + {Packets1, HsState} = dtls_handshake:get_dtls_handshake(Record, HsState0), + State = + State0#state{protocol_buffers = + Buffers#protocol_buffers{dtls_fragment_state = HsState}}, + Events = dtls_handshake_events(Packets0 ++ Packets1), + case StateName of + connection -> + ssl_connection:hibernate_after(StateName, State, Events); + _ -> + {next_state, StateName, State, Events} + end + catch throw:#alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State0) + end; +%%% DTLS record protocol level application data messages +handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]}; +%%% DTLS record protocol level change cipher messages +handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% DTLS record protocol level Alert messages +handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{negotiated_version = Version} = State) -> + case decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State) + end; +%% Ignore unknown TLS record level protocol messages +handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State}. + %%-------------------------------------------------------------------- %% Description:This function is called by a gen_fsm when it is about %% to terminate. It should be the opposite of Module:init/1 and do any @@ -376,7 +437,7 @@ terminate(Reason, StateName, State) -> %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> - {?GEN_STATEM_CB_MODE, StateName, State}. + {ok, StateName, State}. format_status(Type, Data) -> ssl_connection:format_status(Type, Data). @@ -384,10 +445,21 @@ format_status(Type, Data) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- + +dtls_handshake_events([]) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake)); +dtls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + + encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> {Seq, ConnectionStates} = sequence(ConnectionStates0), {EncHandshake, Frag} = dtls_handshake:encode_handshake(Handshake, Version, Seq), - Hist = ssl_handshake:update_handshake_history(Hist0, EncHandshake), + %% DTLS does not have an equivalent version to SSLv2. So v2 hello compatibility + %% will always be false + Hist = ssl_handshake:update_handshake_history(Hist0, EncHandshake, false), {Frag, ConnectionStates, Hist}. encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> @@ -423,12 +495,12 @@ encode_handshake_record(_Version, _Epoch, _Space, _MsgType, _MsgSeq, _Len, <<>>, encode_handshake_record(Version, Epoch, Space, MsgType, MsgSeq, Len, Bin, Offset, MRS, Encoded0, CS0) -> MaxFragmentLen = Space - 25, - case Bin of - <<BinFragment:MaxFragmentLen/bytes, Rest/binary>> -> - ok; + {BinFragment, Rest} = + case Bin of + <<BinFragment0:MaxFragmentLen/bytes, Rest0/binary>> -> + {BinFragment0, Rest0}; _ -> - BinFragment = Bin, - Rest = <<>> + {Bin, <<>>} end, FragLength = byte_size(BinFragment), Frag = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragLength), BinFragment], @@ -459,13 +531,13 @@ finish_pack_records({[], Acc}) -> finish_pack_records({Buf, Acc}) -> lists:reverse([lists:reverse(Buf)|Acc]). -%% decode_alerts(Bin) -> -%% ssl_alert:decode(Bin). +decode_alerts(Bin) -> + ssl_alert:decode(Bin). initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, - ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), + ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -548,7 +620,7 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> {Record, State} = next_record(State0), next_event(StateName, Record, State); _ -> - {Record, State} = read_application_data(<<>>, State0), + {Record, State} = ssl_connection:read_application_data(<<>>, State0), next_event(StateName, Record, State) end. @@ -560,7 +632,7 @@ next_event(connection = StateName, no_record, State0, Actions) -> {no_record, State} -> ssl_connection:hibernate_after(StateName, State, Actions); {#ssl_tls{} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]}; + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; {#alert{} = Alert, State} -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; @@ -569,20 +641,11 @@ next_event(StateName, Record, State, Actions) -> no_record -> {next_state, StateName, State, Actions}; #ssl_tls{} = Record -> - {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]}; + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; #alert{} = Alert -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. -read_application_data(_,State) -> - {#ssl_tls{fragment = <<"place holder">>}, State}. - -handle_own_alert(_,_,_, State) -> %% Place holder - {stop, {shutdown, own_alert}, State}. - -handle_normal_shutdown(_, _, _State) -> %% Place holder - ok. - %% TODO This generates dialyzer warnings, has to be handled differently. %% handle_packet(Address, Port, Packet) -> %% try dtls_record:get_dtls_records(Packet, <<>>) of @@ -631,5 +694,34 @@ handle_normal_shutdown(_, _, _State) -> %% Place holder %% address_to_bin({A,B,C,D,E,F,G,H}, Port) -> %% <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>. -sequence(#connection_states{dtls_write_msg_seq = Seq} = CS) -> - {Seq, CS#connection_states{dtls_write_msg_seq = Seq + 1}}. +sequence(#{write_msg_seq := Seq} = ConnectionState) -> + {Seq, ConnectionState#{write_msg_seq => Seq + 1}}. + +renegotiate(#state{role = client} = State, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_handshake_history(), + {next_state, connection, State#state{tls_handshake_history = Hs0, + protocol_buffers = #protocol_buffers{}}, + [{next_event, internal, #hello_request{}} | Actions]}; + +renegotiate(#state{role = server, + connection_states = CS0} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + CS = CS0#{write_msg_seq => 0}, + State1 = send_handshake(HelloRequest, + State0#state{connection_states = + CS}), + Hs0 = ssl_handshake:init_handshake_history(), + {Record, State} = next_record(State1#state{tls_handshake_history = Hs0, + protocol_buffers = #protocol_buffers{}}), + next_event(hello, Record, State, Actions). + +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop,_} = Stop) -> + Stop; +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 5a799cf441..c6535d5928 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -35,7 +35,7 @@ %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -48,7 +48,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts, Cache, CacheCb, Renegotiation, OwnCert). %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), term(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -61,7 +61,7 @@ client_hello(Host, Port, Cookie, ConnectionStates, Cache, CacheCb, Renegotiation, OwnCert) -> Version = dtls_record:highest_protocol_version(Versions), Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, + SecParams = maps:get(security_parameters, Pending), CipherSuites = ssl_handshake:available_suites(UserSuites, Version), Extensions = ssl_handshake:client_hello_extensions(Host, dtls_v1:corresponding_tls_version(Version), CipherSuites, @@ -445,29 +445,23 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, cookie = Cookie, cipher_suites = CipherSuites, compression_methods = CompMethods, - extensions = HelloExtensions}, Version) -> + extensions = HelloExtensions}, _Version) -> SIDLength = byte_size(SessionID), - BinCookie = enc_client_hello_cookie(Version, Cookie), + CookieLength = byte_size(Cookie), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), - + {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, - BinCookie/binary, + ?BYTE(CookieLength), Cookie/binary, ?UINT16(CsLength), BinCipherSuites/binary, ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; enc_handshake(HandshakeMsg, Version) -> ssl_handshake:encode_handshake(HandshakeMsg, Version). -enc_client_hello_cookie(_, <<>>) -> - <<>>; -enc_client_hello_cookie(_, Cookie) -> - CookieLength = byte_size(Cookie), - <<?BYTE(CookieLength), Cookie/binary>>. - decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, ?BYTE(Cookie_length), Cookie:Cookie_length/binary, diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 5387fcafa8..8a6e2d315c 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -30,7 +30,7 @@ -include("ssl_cipher.hrl"). %% Handling of incoming data --export([get_dtls_records/2]). +-export([get_dtls_records/2, init_connection_states/2]). %% Decoding -export([decode_cipher_text/2]). @@ -58,7 +58,26 @@ %%==================================================================== %% Internal application API %%==================================================================== - +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) -> + ssl_record:connection_states(). +%% % + % +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role, BeastMitigation) -> + ConnectionEnd = ssl_record:record_protocol_role(Role), + Current = initial_connection_state(ConnectionEnd, BeastMitigation), + Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + #{write_msg_seq => 0, + prvious_read => undefined, + current_read => Current, + pending_read => Pending, + prvious_write => undefined, + current_write => Current, + pending_write => Pending}. + %%-------------------------------------------------------------------- -spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. %% @@ -122,63 +141,59 @@ get_dtls_records_aux(Data, Acc) -> end. encode_plain_text(Type, Version, Data, - #connection_states{current_write = - #connection_state{ - epoch = Epoch, - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> + #{current_write := + #{epoch := Epoch, + sequence_number := Seq, + compression_state := CompS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + }= WriteState0} = ConnectionStates) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + WriteState1 = WriteState0#{compression_state => CompS1}, AAD = calc_aad(Type, Version, Epoch, Seq), {CipherFragment, WriteState} = ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, AAD), CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = - WriteState#connection_state{sequence_number = Seq +1}}}; + {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}; encode_plain_text(Type, Version, Data, - #connection_states{current_write=#connection_state{ - epoch = Epoch, - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> + #{current_write := + #{epoch := Epoch, + sequence_number := Seq, + compression_state := CompS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + }= WriteState0} = ConnectionStates) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + WriteState1 = WriteState0#{compression_state => CompS1}, MacHash = calc_mac_hash(WriteState1, Type, Version, Epoch, Seq, Comp), {CipherFragment, WriteState} = ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), Comp, WriteState1, MacHash), CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = - WriteState#connection_state{sequence_number = Seq +1}}}. + {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}. decode_cipher_text(#ssl_tls{type = Type, version = Version, epoch = Epoch, sequence_number = Seq, fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{ - compression_state = CompressionS0, - security_parameters= - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm=CompAlg} - } = ReadState0}= ConnnectionStates0) -> + #{current_read := + #{compression_state := CompressionS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + } = ReadState0} = ConnnectionStates0) -> AAD = calc_aad(Type, Version, Epoch, Seq), case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version), CipherFragment, ReadState0, AAD) of {PlainFragment, ReadState1} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - compression_state = CompressionS1}}, + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState1#{ + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; #alert{} = Alert -> Alert @@ -188,13 +203,12 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, epoch = Epoch, sequence_number = Seq, fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{ - compression_state = CompressionS0, - security_parameters= - #security_parameters{ - compression_algorithm=CompAlg} - } = ReadState0}= ConnnectionStates0) -> + #{current_read := + #{compression_state := CompressionS0, + security_parameters := + #security_parameters{ + compression_algorithm = CompAlg} + } = ReadState0}= ConnnectionStates0) -> {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), CipherFragment, ReadState0, true), MacHash = calc_mac_hash(ReadState1, Type, Version, Epoch, Seq, PlainFragment), @@ -202,17 +216,17 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, true -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - compression_state = CompressionS1}}, + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState1#{ + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end. %%-------------------------------------------------------------------- --spec encode_change_cipher_spec(dtls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_change_cipher_spec(dtls_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes a change_cipher_spec-message to send on the ssl socket. %%-------------------------------------------------------------------- @@ -352,92 +366,87 @@ is_acceptable_version(Version, Versions) -> %%-------------------------------------------------------------------- --spec init_connection_state_seq(dtls_version(), #connection_states{}) -> - #connection_state{}. +-spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) -> + ssl_record:connection_state(). %% %% Description: Copy the read sequence number to the write sequence number %% This is only valid for DTLS in the first client_hello %%-------------------------------------------------------------------- init_connection_state_seq({254, _}, - #connection_states{ - current_read = Read = #connection_state{epoch = 0}, - current_write = Write = #connection_state{epoch = 0}} = CS0) -> - CS0#connection_states{current_write = - Write#connection_state{ - sequence_number = Read#connection_state.sequence_number}}; + #{current_read := #{epoch := 0} = Read, + current_write := #{epoch := 0} = Write} = CS0) -> + Seq = maps:get(sequence_number, Read), + CS0#{current_write => Write#{sequence_number => Seq}}; init_connection_state_seq(_, CS) -> CS. %%-------------------------------------------------------- --spec current_connection_state_epoch(#connection_states{}, read | write) -> +-spec current_connection_state_epoch(ssl_record:connection_states(), read | write) -> integer(). %% %% Description: Returns the epoch the connection_state record %% that is currently defined as the current conection state. %%-------------------------------------------------------------------- -current_connection_state_epoch(#connection_states{current_read = Current}, +current_connection_state_epoch(#{current_read := Current}, read) -> - Current#connection_state.epoch; -current_connection_state_epoch(#connection_states{current_write = Current}, + maps:get(epoch, Current); +current_connection_state_epoch(#{current_write := Current}, write) -> - Current#connection_state.epoch. + maps:get(epoch, Current). %%-------------------------------------------------------------------- --spec connection_state_by_epoch(#connection_states{}, integer(), read | write) -> - #connection_state{}. +-spec connection_state_by_epoch(ssl_record:connection_states(), integer(), read | write) -> + ssl_record:connection_state(). %% %% Description: Returns the instance of the connection_state record %% that is defined by the Epoch. %%-------------------------------------------------------------------- -connection_state_by_epoch(#connection_states{current_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> +connection_state_by_epoch(#{current_read := #{epoch := Epoch}} = CS, Epoch, read) -> CS; -connection_state_by_epoch(#connection_states{pending_read = CS}, Epoch, read) - when CS#connection_state.epoch == Epoch -> +connection_state_by_epoch(#{pending_read := #{epoch := Epoch}} = CS, Epoch, read) -> CS; -connection_state_by_epoch(#connection_states{current_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> +connection_state_by_epoch(#{current_write := #{epoch := Epoch}} = CS, Epoch, write) -> CS; -connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) - when CS#connection_state.epoch == Epoch -> +connection_state_by_epoch(#{pending_write := #{epoch := Epoch}} = CS, Epoch, write) -> CS. %%-------------------------------------------------------------------- --spec set_connection_state_by_epoch(#connection_states{}, - #connection_state{}, read | write) - -> #connection_states{}. +-spec set_connection_state_by_epoch(ssl_record:connection_states(), + ssl_record:connection_state(), read | write) + -> ssl_record:connection_states(). %% %% Description: Returns the instance of the connection_state record %% that is defined by the Epoch. %%-------------------------------------------------------------------- -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_read = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_read = CS}, - NewCS = #connection_state{epoch = Epoch}, read) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_read = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{current_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{current_write = NewCS}; - -set_connection_state_by_epoch(ConnectionStates0 = - #connection_states{pending_write = CS}, - NewCS = #connection_state{epoch = Epoch}, write) - when CS#connection_state.epoch == Epoch -> - ConnectionStates0#connection_states{pending_write = NewCS}. +set_connection_state_by_epoch(#{current_read := #{epoch := Epoch}} = ConnectionStates0, + NewCS = #{epoch := Epoch}, read) -> + ConnectionStates0#{current_read => NewCS}; +set_connection_state_by_epoch(#{pending_read := #{epoch := Epoch}} = ConnectionStates0, + NewCS = #{epoch := Epoch}, read) -> + ConnectionStates0#{pending_read => NewCS}; +set_connection_state_by_epoch(#{current_write := #{epoch := Epoch}} = ConnectionStates0, + NewCS = #{epoch := Epoch}, write) -> + ConnectionStates0#{current_write => NewCS}; +set_connection_state_by_epoch(#{pending_write := #{epoch := Epoch}} = ConnectionStates0, +NewCS = #{epoch := Epoch}, write) -> + ConnectionStates0#{pending_write => NewCS}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #{security_parameters => + ssl_record:initial_security_params(ConnectionEnd), + epoch => 0, + sequence_number => 1, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. lowest_list_protocol_version(Ver, []) -> Ver; @@ -454,8 +463,8 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), ?UINT48(Seq), ?UINT16(Length)>>, Fragment]. -calc_mac_hash(#connection_state{mac_secret = MacSecret, - security_parameters = #security_parameters{mac_algorithm = MacAlg}}, +calc_mac_hash(#{mac_secret := MacSecret, + security_parameters := #security_parameters{mac_algorithm = MacAlg}}, Type, Version, Epoch, SeqNo, Fragment) -> Length = erlang:iolist_size(Fragment), NewSeq = (Epoch bsl 48) + SeqNo, diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index b26efbd88f..00b0513891 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -55,7 +55,7 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-3.0","public_key-1.2","kernel-3.0", + {runtime_dependencies, ["stdlib-3.1","public_key-1.2","kernel-3.0", "erts-7.0","crypto-3.3", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index fadc67ef80..32252386b4 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,24 +1,11 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"^8[.]0$">>, - [{load_module, ssl_handshake, soft_purge, soft_purge, []} - ]}, - {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, - {<<"6\\..*">>, [{restart_application, ssl}]}, - {<<"5\\..*">>, [{restart_application, ssl}]}, - {<<"4\\..*">>, [{restart_application, ssl}]}, - {<<"3\\..*">>, [{restart_application, ssl}]} + {<<"^8[.]0([.][0-9]+)?$">>, [{restart_application, ssl}]}, + {<<"^[3-7][.][^.].*">>, [{restart_application, ssl}]} ], [ - {<<"^8[.]0$">>, - [{load_module, ssl_handshake, soft_purge, soft_purge, []} - ]}, - {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, - {<<"6\\..*">>, [{restart_application, ssl}]}, - {<<"5\\..*">>, [{restart_application, ssl}]}, - {<<"4\\..*">>, [{restart_application, ssl}]}, - {<<"3\\..*">>, [{restart_application, ssl}]} - ] + {<<"^8[.]0([.][0-9]+)?$">>, [{restart_application, ssl}]}, + {<<"^[3-7][.][^.].*">>, [{restart_application, ssl}]} + ] }. - diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index d2aeb3258f..27b753af2e 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -43,7 +43,7 @@ renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([handle_options/2]). +-export([handle_options/2, tls_version/1]). -deprecated({negotiated_next_protocol, 1, next_major_release}). -deprecated({connection_info, 1, next_major_release}). @@ -607,6 +607,11 @@ format_error(Error) -> Other end. +tls_version({3, _} = Version) -> + Version; +tls_version({254, _} = Version) -> + dtls_v1:corresponding_tls_version(Version). + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index db71b16d80..05dfb4c1b3 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -39,8 +39,8 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec encode(#alert{}, ssl_record:ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes an alert %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 3ec3f50e05..f359655d85 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -64,7 +64,7 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) - {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), {self, IssuerId}; false -> - other_issuer(OtpCert, BinCert, CertDbHandle) + other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) end, case SignedAndIssuerID of @@ -200,7 +200,7 @@ certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) -> {_, true = SelfSigned} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, BinCert, CertDbHandle) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) of {ok, {SerialNr, Issuer}} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned); @@ -232,7 +232,7 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned {ok, undefined, lists:reverse(Chain)} end. -find_issuer(OtpCert, BinCert, CertDbHandle) -> +find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) -> IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of @@ -250,12 +250,24 @@ find_issuer(OtpCert, BinCert, CertDbHandle) -> Acc end, - try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of - issuer_not_found -> - {error, issuer_not_found} - catch - {ok, _IssuerId} = Return -> - Return + if is_reference(CertsDbRef) -> % actual DB exists + try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _IssuerId} = Return -> + Return + end; + is_tuple(CertsDbRef), element(1,CertsDbRef) =:= extracted -> % cache bypass byproduct + {extracted, CertsData} = CertsDbRef, + DB = [Entry || {decoded, Entry} <- CertsData], + try lists:foldl(IsIssuerFun, issuer_not_found, DB) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _IssuerId} = Return -> + Return + end end. is_valid_extkey_usage(KeyUse, client) -> @@ -281,12 +293,12 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith subjectPublicKey = Key}) -> {Key, Params}. -other_issuer(OtpCert, BinCert, CertDbHandle) -> +other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) -> case public_key:pkix_issuer_id(OtpCert, other) of {ok, IssuerId} -> {other, IssuerId}; {error, issuer_not_found} -> - case find_issuer(OtpCert, BinCert, CertDbHandle) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) of {ok, IssuerId} -> {other, IssuerId}; Other -> diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 53282998d0..304d1706f5 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -58,6 +58,13 @@ -export([handle_info/3, handle_call/5, handle_session/7, ssl_config/3, prepare_connection/2, hibernate_after/3]). +%% Alert and close handling +-export([handle_own_alert/4,handle_alert/3, + handle_normal_shutdown/3 + ]). + +%% Data handling +-export([write_application_data/3, read_application_data/2]). %%==================================================================== %% Internal application API @@ -264,7 +271,7 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %%-------------------------------------------------------------------- -spec handle_session(#server_hello{}, ssl_record:ssl_version(), - binary(), #connection_states{}, _,_, #state{}) -> + binary(), ssl_record:connection_states(), _,_, #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- handle_session(#server_hello{cipher_suite = CipherSuite, @@ -272,19 +279,21 @@ handle_session(#server_hello{cipher_suite = CipherSuite, Version, NewId, ConnectionStates, ProtoExt, Protocol0, #state{session = #session{session_id = OldId}, negotiated_version = ReqVersion, - negotiated_protocol = CurrentProtocol} = State0) -> + negotiated_protocol = CurrentProtocol} = State0) -> {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - {ExpectNPN, Protocol} = case Protocol0 of - undefined -> {false, CurrentProtocol}; - _ -> {ProtoExt =:= npn, Protocol0} - end, + {ExpectNPN, Protocol} = case Protocol0 of + undefined -> + {false, CurrentProtocol}; + _ -> + {ProtoExt =:= npn, Protocol0} + end, State = State0#state{key_algorithm = KeyAlgorithm, - negotiated_version = Version, + negotiated_version = Version, connection_states = ConnectionStates, premaster_secret = PremasterSecret, expecting_next_protocol_negotiation = ExpectNPN, @@ -382,7 +391,7 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, session = #session{master_secret = MasterSecret}, connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, client, + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, get_current_prf(ConnectionStates0, write), MasterSecret, Handshake) of verified -> @@ -392,7 +401,7 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, expecting_finished = false}, Connection), Connection:next_event(connection, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, abbreviated, State0) + handle_own_alert(Alert, Version, abbreviated, State0) end; abbreviated(internal, #finished{verify_data = Data} = Finished, @@ -400,7 +409,7 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, session = #session{master_secret = MasterSecret}, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, server, + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, get_pending_prf(ConnectionStates0, write), MasterSecret, Handshake0) of verified -> @@ -412,7 +421,7 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), Connection:next_event(connection, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, abbreviated, State0) + handle_own_alert(Alert, Version, abbreviated, State0) end; %% only allowed to send next_protocol message after change cipher spec @@ -452,9 +461,9 @@ certify(internal, #certificate{asn1_certificates = []}, #state{role = server, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = - State, Connection) -> + State, _Connection) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - Connection:handle_own_alert(Alert, Version, certify, State); + handle_own_alert(Alert, Version, certify, State); certify(internal, #certificate{asn1_certificates = []}, #state{role = server, @@ -469,9 +478,9 @@ certify(internal, #certificate{}, #state{role = server, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_none}} = - State, Connection) -> + State, _Connection) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), - Connection:handle_own_alert(Alert, Version, certify, State); + handle_own_alert(Alert, Version, certify, State); certify(internal, #certificate{} = Cert, #state{negotiated_version = Version, @@ -492,7 +501,7 @@ certify(internal, #certificate{} = Cert, handle_peer_cert(Role, PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, certify, State) end; certify(internal, #server_key_exchange{exchange_keys = Keys}, @@ -506,10 +515,10 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, Alg == psk; Alg == dhe_psk; Alg == rsa_psk; Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> - Params = ssl_handshake:decode_server_key(Keys, Alg, Version), + Params = ssl_handshake:decode_server_key(Keys, Alg, ssl:tls_version(Version)), %% Use negotiated value if TLS-1.2 otherwhise return default - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, Version), + HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, ssl:tls_version(Version)), case is_anonymous(Alg) of true -> @@ -517,26 +526,25 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, State#state{hashsign_algorithm = HashSign}, Connection); false -> case ssl_handshake:verify_server_key(Params, HashSign, - ConnectionStates, Version, PubKeyInfo) of + ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of true -> calculate_secret(Params#server_key_params.params, State#state{hashsign_algorithm = HashSign}, Connection); false -> - Connection:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), + handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), Version, certify, State) end end; -certify(internal, #certificate_request{hashsign_algorithms = HashSigns}, +certify(internal, #certificate_request{} = CertRequest, #state{session = #session{own_certificate = Cert}, - key_algorithm = KeyExAlg, + role = client, ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, negotiated_version = Version} = State0, Connection) -> - - case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of #alert {} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, certify, State0); NegotiatedHashSign -> {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), Connection:next_event(certify, Record, @@ -555,7 +563,7 @@ certify(internal, #server_hello_done{}, when Alg == psk -> case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, certify, State0); PremasterSecret -> State = master_secret(PremasterSecret, State0#state{premaster_secret = PremasterSecret}), @@ -576,7 +584,7 @@ certify(internal, #server_hello_done{}, case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0); + handle_own_alert(Alert, Version, certify, State0); PremasterSecret -> State = master_secret(PremasterSecret, State0#state{premaster_secret = RSAPremasterSecret}), @@ -590,13 +598,13 @@ certify(internal, #server_hello_done{}, negotiated_version = Version, premaster_secret = undefined, role = client} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> State = State0#state{connection_states = ConnectionStates}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end; %% Master secret is calculated from premaster_secret @@ -606,7 +614,7 @@ certify(internal, #server_hello_done{}, negotiated_version = Version, premaster_secret = PremasterSecret, role = client} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, @@ -614,7 +622,7 @@ certify(internal, #server_hello_done{}, session = Session}, client_certify_and_key_exchange(State, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end; certify(internal = Type, #client_key_exchange{} = Msg, @@ -628,11 +636,11 @@ certify(internal = Type, #client_key_exchange{} = Msg, certify(internal, #client_key_exchange{exchange_keys = Keys}, State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) -> try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), State, Connection) catch #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State) + handle_own_alert(Alert, Version, certify, State) end; certify(Type, Msg, State, Connection) -> @@ -663,21 +671,21 @@ cipher(internal, #certificate_verify{signature = Signature, %% Use negotiated value if TLS-1.2 otherwhise return default HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, Version), case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - Version, HashSign, MasterSecret, Handshake) of + ssl:tls_version(Version), HashSign, MasterSecret, Handshake) of valid -> {Record, State} = Connection:next_record(State0), Connection:next_event(cipher, Record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, cipher, State0) + handle_own_alert(Alert, Version, cipher, State0) end; %% client must send a next protocol message if we are expecting it cipher(internal, #finished{}, #state{role = server, expecting_next_protocol_negotiation = true, negotiated_protocol = undefined, negotiated_version = Version} = State0, - Connection) -> - Connection:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); + _Connection) -> + handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); cipher(internal, #finished{verify_data = Data} = Finished, #state{negotiated_version = Version, @@ -689,7 +697,7 @@ cipher(internal, #finished{verify_data = Data} = Finished, = Session0, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State, Connection) -> - case ssl_handshake:verify_connection(Version, Finished, + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, opposite_role(Role), get_current_prf(ConnectionStates0, read), MasterSecret, Handshake0) of @@ -698,7 +706,7 @@ cipher(internal, #finished{verify_data = Data} = Finished, cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, cipher, State) + handle_own_alert(Alert, Version, cipher, State) end; %% only allowed to send next_protocol message after change cipher spec @@ -731,7 +739,7 @@ connection({call, From}, {application_data, Data}, %% parallize send and receive decoding and not block the receiver %% if sending is overloading the socket. try - Connection:write_application_data(Data, From, State) + write_application_data(Data, From, State) catch throw:Error -> hibernate_after(connection, State, [{reply, From, Error}]) end; @@ -802,35 +810,37 @@ handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #st when StateName =/= connection -> {keep_state_and_data}; handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, - #state{tls_handshake_history = Hs0} = State0, Connection) -> + #state{tls_handshake_history = Hs0, + ssl_options = #ssl_options{v2_hello_compatible = V2HComp}} = State0, + Connection) -> + + PossibleSNI = Connection:select_sni_extension(Handshake), %% This function handles client SNI hello extension when Handshake is %% a client_hello, which needs to be determined by the connection callback. %% In other cases this is a noop - State = Connection:handle_sni_extension(Handshake, State0), - HsHist = ssl_handshake:update_handshake_history(Hs0, Raw), + State = handle_sni_extension(PossibleSNI, State0), + HsHist = ssl_handshake:update_handshake_history(Hs0, Raw, V2HComp), {next_state, StateName, State#state{tls_handshake_history = HsHist}, [{next_event, internal, Handshake}]}; -handle_common_event(internal, {tls_record, TLSRecord}, StateName, State, Connection) -> - Connection:handle_common_event(internal, TLSRecord, StateName, State); +handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) -> + Connection:handle_common_event(internal, TLSorDTLSRecord, StateName, State); handle_common_event(timeout, hibernate, _, _, _) -> {keep_state_and_data, [hibernate]}; handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) -> - case Connection:read_application_data(Data, State0) of + case read_application_data(Data, State0) of {stop, Reason, State} -> {stop, Reason, State}; {Record, State} -> Connection:next_event(StateName, Record, State) end; handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, - #state{negotiated_version = Version} = State, Connection) -> - Connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, + #state{negotiated_version = Version} = State, _) -> + handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State); -handle_common_event(internal, _, _, _, _) -> - {keep_state_and_data, [postpone]}; handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = State, - Connection) -> + _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), - Connection:handle_own_alert(Alert, Version, {StateName, Msg}, State). + handle_own_alert(Alert, Version, {StateName, Msg}, State). handle_call({application_data, _Data}, _, _, _, _) -> %% In renegotiation priorities handshake, send data when handshake is finished @@ -908,9 +918,8 @@ handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, #state{connection_states = ConnectionStates, negotiated_version = Version}, _) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:current_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{master_secret = MasterSecret, client_random = ClientRandom, server_random = ServerRandom, @@ -925,7 +934,7 @@ handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, (client_random, Acc) -> [ClientRandom|Acc]; (server_random, Acc) -> [ServerRandom|Acc] end, [], Seed)), - ssl_handshake:prf(Version, PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) + ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) catch exit:_ -> {error, badarg}; error:Reason -> {error, Reason} @@ -936,20 +945,19 @@ handle_call(_,_,_,_,_) -> handle_info({ErrorTag, Socket, econnaborted}, StateName, #state{socket = Socket, transport_cb = Transport, - start_or_recv_from = StartFrom, role = Role, protocol_cb = Connection, + start_or_recv_from = StartFrom, role = Role, error_tag = ErrorTag, tracker = Tracker} = State) when StateName =/= connection -> - Connection:alert_user(Transport, Tracker,Socket, - StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), + alert_user(Transport, Tracker,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), {stop, normal, State}; handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - protocol_cb = Connection, error_tag = ErrorTag} = State) -> Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), error_logger:info_report(Report), - Connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), {stop, normal, State}; handle_info({'DOWN', MonitorRef, _, _, _}, _, @@ -1052,13 +1060,124 @@ format_status(terminate, [_, StateName, State]) -> ssl_options = NewOptions, flight_buffer = ?SECRET_PRINTOUT} }}]}]. + +%%-------------------------------------------------------------------- +%%% +%%-------------------------------------------------------------------- +write_application_data(Data0, From, + #state{socket = Socket, + negotiated_version = Version, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> + Data = encode_packet(Data0, SockOpts), + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + Connection:renegotiate(State#state{renegotiation = {true, internal}}, + [{next_event, {call, From}, {application_data, Data0}}]); + false -> + {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), + Result = Transport:send(Socket, Msgs), + ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates}, + [{reply, From, Result}]) + end. + +read_application_data(Data, #state{user_application = {_Mon, Pid}, + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + socket_options = SOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + timer = Timer, + user_data_buffer = Buffer0, + tracker = Tracker} = State0) -> + Buffer1 = if + Buffer0 =:= <<>> -> Data; + Data =:= <<>> -> Buffer0; + true -> <<Buffer0/binary, Data/binary>> + end, + case get_data(SOpts, BytesToRead, Buffer1) of + {ok, ClientData, Buffer} -> % Send data + SocketOpt = deliver_app_data(Transport, Socket, SOpts, + ClientData, Pid, RecvFrom, Tracker, Connection), + cancel_timer(Timer), + State = State0#state{user_data_buffer = Buffer, + start_or_recv_from = undefined, + timer = undefined, + bytes_to_read = undefined, + socket_options = SocketOpt + }, + if + SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv + %% Active and empty, get more data + Connection:next_record_if_active(State); + true -> %% We have more data + read_application_data(<<>>, State) + end; + {more, Buffer} -> % no reply, we need more data + Connection:next_record(State0#state{user_data_buffer = Buffer}); + {passive, Buffer} -> + Connection:next_record_if_active(State0#state{user_data_buffer = Buffer}); + {error,_Reason} -> %% Invalid packet in packet mode + deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), + {stop, normal, State0} + end. +%%-------------------------------------------------------------------- +%%% +%%-------------------------------------------------------------------- +handle_alert(#alert{level = ?FATAL} = Alert, StateName, + #state{socket = Socket, transport_cb = Transport, + protocol_cb = Connection, + ssl_options = SslOpts, start_or_recv_from = From, host = Host, + port = Port, session = Session, user_application = {_Mon, Pid}, + role = Role, socket_options = Opts, tracker = Tracker}) -> + invalidate_session(Role, Host, Port, Session), + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), + {stop, normal}; + +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + StateName, State) -> + handle_normal_shutdown(Alert, StateName, State), + {stop, {shutdown, peer_close}}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + handle_normal_shutdown(Alert, StateName, State), + {stop, {shutdown, peer_close}}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{ssl_options = SslOpts, renegotiation = {true, From}, + protocol_cb = Connection} = State0) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + gen_statem:reply(From, {error, renegotiation_rejected}), + {Record, State} = Connection:next_record(State0), + %% Go back to connection! + Connection:next_event(connection, Record, State); + +%% Gracefully log and ignore all other warning alerts +handle_alert(#alert{level = ?WARNING} = Alert, StateName, + #state{ssl_options = SslOpts, protocol_cb = Connection} = State0) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + {Record, State} = Connection:next_record(State0), + Connection:next_event(StateName, Record, State). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- connection_info(#state{sni_hostname = SNIHostname, session = #session{cipher_suite = CipherSuite}, - negotiated_version = Version, ssl_options = Opts}) -> - [{protocol, tls_record:protocol_version(Version)}, + protocol_cb = Connection, + negotiated_version = {_,_} = Version, + ssl_options = Opts}) -> + RecordCB = record_cb(Connection), + [{protocol, RecordCB:protocol_version(Version)}, {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)}, {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts). @@ -1070,7 +1189,7 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol = State0, Connection) when is_atom(Type) -> ServerHello = - ssl_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt), + ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates0, ServerHelloExt), State = server_hello(ServerHello, State0#state{expecting_next_protocol_negotiation = NextProtocols =/= undefined}, Connection), @@ -1097,14 +1216,14 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, Connection:next_event(certify, Record, State) catch #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State0) end. resumed_server_hello(#state{session = Session, connection_states = ConnectionStates0, negotiated_version = Version} = State0, Connection) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, server) of {_, ConnectionStates1} -> State1 = State0#state{connection_states = ConnectionStates1, @@ -1114,7 +1233,7 @@ resumed_server_hello(#state{session = Session, {Record, State} = Connection:next_record(State2), Connection:next_event(abbreviated, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State0) end. server_hello(ServerHello, State0, Connection) -> @@ -1180,7 +1299,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, tls_handshake_history = Handshake0} = State, Connection) -> case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - Version, HashSign, PrivateKey, Handshake0) of + ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of #certificate_verify{} = Verified -> Connection:queue_handshake(Verified, State); ignore -> @@ -1203,7 +1322,7 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = Connection:next_event(cipher, Record, State) catch throw:#alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end. do_client_certify_and_key_exchange(State0, Connection) -> @@ -1294,12 +1413,11 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo == dhe_rsa; Algo == dh_anon -> DHKeys = public_key:generate_key(Params), - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1319,15 +1437,15 @@ key_exchange(#state{role = server, key_algorithm = Algo, Algo == ecdh_anon -> ECDHKeys = public_key:generate_key(select_curve(State0)), - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; @@ -1341,14 +1459,14 @@ key_exchange(#state{role = server, key_algorithm = psk, connection_states = ConnectionStates0, negotiated_version = Version } = State0, Connection) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); @@ -1361,16 +1479,16 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, negotiated_version = Version } = State0, Connection) -> DHKeys = public_key:generate_key(Params), - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, - PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {dhe_psk, + PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; @@ -1384,15 +1502,15 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk, connection_states = ConnectionStates0, negotiated_version = Version } = State0, Connection) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), Connection:queue_handshake(Msg, State0); key_exchange(#state{role = server, key_algorithm = Algo, @@ -1413,15 +1531,15 @@ key_exchange(#state{role = server, key_algorithm = Algo, Keys0 = {_,_} -> Keys0 end, - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{srp_params = SrpParams, srp_keys = Keys}; @@ -1431,7 +1549,7 @@ key_exchange(#state{role = client, public_key_info = PublicKeyInfo, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> - Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), + Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); key_exchange(#state{role = client, @@ -1442,7 +1560,7 @@ key_exchange(#state{role = client, when Algorithm == dhe_dss; Algorithm == dhe_rsa; Algorithm == dh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), Connection:queue_handshake(Msg, State0); key_exchange(#state{role = client, @@ -1452,14 +1570,14 @@ key_exchange(#state{role = client, when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; Algorithm == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Keys}), Connection:queue_handshake(Msg, State0); key_exchange(#state{role = client, ssl_options = SslOpts, key_algorithm = psk, negotiated_version = Version} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, Version, + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {psk, SslOpts#ssl_options.psk_identity}), Connection:queue_handshake(Msg, State0); @@ -1468,7 +1586,7 @@ key_exchange(#state{role = client, key_algorithm = dhe_psk, negotiated_version = Version, diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, Version, + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), Connection:queue_handshake(Msg, State0); @@ -1479,7 +1597,7 @@ key_exchange(#state{role = client, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> - Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, + Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); @@ -1491,7 +1609,7 @@ key_exchange(#state{role = client, when Algorithm == srp_dss; Algorithm == srp_rsa; Algorithm == srp_anon -> - Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), Connection:queue_handshake(Msg, State0). rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) @@ -1504,7 +1622,7 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - ssl_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, ssl:tls_version(Version), {premaster_secret, PremasterSecret, PublicKeyInfo}); rsa_key_exchange(_, _, _) -> @@ -1521,7 +1639,7 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, Algorithm == ?sha384WithRSAEncryption; Algorithm == ?sha512WithRSAEncryption -> - ssl_handshake:key_exchange(client, Version, + ssl_handshake:key_exchange(client, ssl:tls_version(Version), {psk_premaster_secret, PskIdentity, PremasterSecret, PublicKeyInfo}); rsa_psk_key_exchange(_, _, _, _) -> @@ -1533,12 +1651,14 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, cert_db = CertDbHandle, cert_db_ref = CertDbRef, negotiated_version = Version} = State0, Connection) -> - #connection_state{security_parameters = - #security_parameters{cipher_suite = CipherSuite}} = + #{security_parameters := + #security_parameters{cipher_suite = CipherSuite}} = ssl_record:pending_connection_state(ConnectionStates0, read), - HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, Version, [Version]), + TLSVersion = ssl:tls_version(Version), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, + TLSVersion, [TLSVersion]), Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, - HashSigns, Version), + HashSigns, TLSVersion), State = Connection:queue_handshake(Msg, State0), State#state{client_certificate_requested = true}; @@ -1551,7 +1671,7 @@ calculate_master_secret(PremasterSecret, connection_states = ConnectionStates0, session = Session0} = State0, Connection, _Current, Next) -> - case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, @@ -1560,7 +1680,7 @@ calculate_master_secret(PremasterSecret, {Record, State} = Connection:next_record(State1), Connection:next_event(Next, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, certify, State0) + handle_own_alert(Alert, Version, certify, State0) end. finalize_handshake(State0, StateName, Connection) -> @@ -1593,7 +1713,7 @@ finished(#state{role = Role, negotiated_version = Version, connection_states = ConnectionStates0, tls_handshake_history = Handshake0} = State0, StateName, Connection) -> MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(Version, Role, + Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, get_current_prf(ConnectionStates0, write), MasterSecret, Handshake0), ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), @@ -1658,7 +1778,7 @@ master_secret(#alert{} = Alert, _) -> master_secret(PremasterSecret, #state{session = Session, negotiated_version = Version, role = Role, connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, Role) of {MasterSecret, ConnectionStates} -> State#state{ @@ -1741,11 +1861,11 @@ is_anonymous(_) -> false. get_current_prf(CStates, Direction) -> - CS = ssl_record:current_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. + #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. get_pending_prf(CStates, Direction) -> - CS = ssl_record:pending_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. + #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. opposite_role(client) -> server; @@ -1967,7 +2087,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, session_cache = Cache, session_cache_cb = CacheCb} = State0) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case ssl_handshake:master_secret(tls_record, Version, Session, + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {_, ConnectionStates} -> {Record, State} = @@ -1976,7 +2096,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, session = Session}), Connection:next_event(abbreviated, Record, State); #alert{} = Alert -> - Connection:handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State0) end. make_premaster_secret({MajVer, MinVer}, rsa) -> @@ -2043,7 +2163,7 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = %% user_data_buffer =/= <<>> handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) -> - case Connection:read_application_data(<<>>, State0) of + case read_application_data(<<>>, State0) of {stop, Reason, State} -> {stop, Reason, State}; {Record, State1} -> @@ -2057,3 +2177,264 @@ handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} Stop end end. + +encode_packet(Data, #socket_options{packet=Packet}) -> + case Packet of + 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); + 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); + 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); + _ -> Data + end. + +encode_size_packet(Bin, Size, Max) -> + Len = erlang:byte_size(Bin), + case Len > Max of + true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); + false -> <<Len:Size, Bin/binary>> + end. + +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), + %% but we chose to have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. + + +%% Picks ClientData +get_data(_, _, <<>>) -> + {more, <<>>}; +%% Recv timed out save buffer data until next recv +get_data(#socket_options{active=false}, undefined, Buffer) -> + {passive, Buffer}; +get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) + when Raw =:= raw; Raw =:= 0 -> %% Raw Mode + if + Active =/= false orelse BytesToRead =:= 0 -> + %% Active true or once, or passive mode recv(0) + {ok, Buffer, <<>>}; + byte_size(Buffer) >= BytesToRead -> + %% Passive Mode, recv(Bytes) + <<Data:BytesToRead/binary, Rest/binary>> = Buffer, + {ok, Data, Rest}; + true -> + %% Passive Mode not enough data + {more, Buffer} + end; +get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> + PacketOpts = [{packet_size, Size}], + case decode_packet(Type, Buffer, PacketOpts) of + {more, _} -> + {more, Buffer}; + Decoded -> + Decoded + end. + +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, + Data, Pid, From, Tracker, Connection) -> + send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker, Connection)), + SO = case Data of + {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + % End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + _ -> + SO + end. + +format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data, _, _) -> + {ok, do_format_reply(Mode, Packet, Header, Data)}; +format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, + header = Header}, Data, Tracker, Connection) -> + {ssl, ssl_socket:socket(self(), Transport, Socket, Connection, Tracker), + do_format_reply(Mode, Packet, Header, Data)}. + +deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) -> + send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker, Connection)). + +format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> + {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; +format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker, Connection) -> + {ssl_error, ssl_socket:socket(self(), Transport, Socket, Connection, Tracker), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + +do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +do_format_reply(binary, _, _, Data) -> + Data; +do_format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> + Data; +do_format_reply(list, _,_, Data) -> + binary_to_list(Data). + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <<?BYTE(ByteN), NewBinary/binary>> = Binary, + [ByteN | header(N-1, NewBinary)]. + +send_or_reply(false, _Pid, From, Data) when From =/= undefined -> + gen_statem:reply(From, Data); +%% Can happen when handling own alert or tcp error/close and there is +%% no outstanding gen_fsm sync events +send_or_reply(false, no_pid, _, _) -> + ok; +send_or_reply(_, Pid, _From, Data) -> + send_user(Pid, Data). + +send_user(Pid, Msg) -> + Pid ! Msg. + +alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) -> + alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection); +alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) -> + alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection). + +alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection) -> + alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection). + +alert_user(_, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined -> + %% If there is an outstanding ssl_accept | recv + %% From will be defined and send_or_reply will + %% send the appropriate error message. + ReasonCode = ssl_alert:reason_code(Alert, Role), + send_or_reply(Active, Pid, From, {error, ReasonCode}); +alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) -> + case ssl_alert:reason_code(Alert, Role) of + closed -> + send_or_reply(Active, Pid, From, + {ssl_closed, ssl_socket:socket(self(), + Transport, Socket, Connection, Tracker)}); + ReasonCode -> + send_or_reply(Active, Pid, From, + {ssl_error, ssl_socket:socket(self(), + Transport, Socket, Connection, Tracker), ReasonCode}) + end. + +log_alert(true, Info, Alert) -> + Txt = ssl_alert:alert_txt(Alert), + error_logger:format("SSL: ~p: ~s\n", [Info, Txt]); +log_alert(false, _, _) -> + ok. + +handle_own_alert(Alert, Version, StateName, + #state{transport_cb = Transport, + socket = Socket, + connection_states = ConnectionStates, + ssl_options = SslOpts} = State) -> + try %% Try to tell the other side + {BinMsg, _} = + ssl_alert:encode(Alert, Version, ConnectionStates), + Transport:send(Socket, BinMsg) + catch _:_ -> %% Can crash if we are in a uninitialized state + ignore + end, + try %% Try to tell the local user + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + handle_normal_shutdown(Alert,StateName, State) + catch _:_ -> + ok + end, + {stop, {shutdown, own_alert}}. + +handle_normal_shutdown(Alert, _, #state{socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + start_or_recv_from = StartFrom, + tracker = Tracker, + role = Role, renegotiation = {false, first}}) -> + alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); + +handle_normal_shutdown(Alert, StateName, #state{socket = Socket, + socket_options = Opts, + transport_cb = Transport, + protocol_cb = Connection, + user_application = {_Mon, Pid}, + tracker = Tracker, + start_or_recv_from = RecvFrom, role = Role}) -> + alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). + +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, Port, Session) -> + ssl_manager:invalidate_session(Port, Session). + +handle_sni_extension(undefined, State) -> + State; +handle_sni_extension(#sni{hostname = Hostname}, State0) -> + NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), + case NewOptions of + undefined -> + State0; + _ -> + {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbHandle, OwnCert, Key, DHParams} = + ssl_config:init(NewOptions, State0#state.role), + State0#state{ + session = State0#state.session#session{own_certificate = OwnCert}, + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = NewOptions, + sni_hostname = Hostname + } + end. + +update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) -> + SSLOption = + case OrigSSLOptions#ssl_options.sni_fun of + undefined -> + proplists:get_value(SNIHostname, + OrigSSLOptions#ssl_options.sni_hosts); + SNIFun -> + SNIFun(SNIHostname) + end, + case SSLOption of + undefined -> + undefined; + _ -> + ssl:handle_options(SSLOption, OrigSSLOptions) + end. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 4b54943ddf..f1e612a41b 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -46,7 +46,7 @@ socket :: port(), ssl_options :: #ssl_options{}, socket_options :: #socket_options{}, - connection_states :: #connection_states{} | secret_printout(), + connection_states :: ssl_record:connection_states() | secret_printout(), protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() | 'undefined', diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl index d9f21e04ac..01be1fb9ab 100644 --- a/lib/ssl/src/ssl_crl.erl +++ b/lib/ssl/src/ssl_crl.erl @@ -47,7 +47,7 @@ trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbHandle) -> {ok, unknown_crl_ca, []} end. -find_issuer(CRL, {Db,_}) -> +find_issuer(CRL, {Db,DbRef}) -> Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), IsIssuerFun = fun({_Key, {_Der,ErlCertCandidate}}, Acc) -> @@ -55,15 +55,27 @@ find_issuer(CRL, {Db,_}) -> (_, Acc) -> Acc end, - - try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, Db) of - issuer_not_found -> - {error, issuer_not_found} - catch - {ok, _} = Result -> - Result + if is_reference(DbRef) -> % actual DB exists + try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, Db) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _} = Result -> + Result + end; + is_tuple(DbRef), element(1,DbRef) =:= extracted -> % cache bypass byproduct + {extracted, CertsData} = DbRef, + Certs = [Entry || {decoded, Entry} <- CertsData], + try lists:foldl(IsIssuerFun, issuer_not_found, Certs) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _} = Result -> + Result + end end. + verify_crl_issuer(CRL, ErlCertCandidate, Issuer, NotIssuer) -> TBSCert = ErlCertCandidate#'OTPCertificate'.tbsCertificate, case public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.subject) of diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 06226d5155..5b51ac0916 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -51,8 +51,8 @@ %% Handle handshake messages -export([certify/10, client_certificate_verify/6, certificate_verify/6, verify_signature/5, - master_secret/5, server_key_exchange_hash/2, verify_connection/6, - init_handshake_history/0, update_handshake_history/2, verify_server_key/5 + master_secret/4, server_key_exchange_hash/2, verify_connection/6, + init_handshake_history/0, update_handshake_history/3, verify_server_key/5 ]). %% Encode/Decode @@ -74,7 +74,7 @@ ]). %% MISC --export([select_version/3, prf/6, select_hashsign/5, +-export([select_version/3, prf/6, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, premaster_secret/2, premaster_secret/3, premaster_secret/4]). @@ -94,15 +94,14 @@ hello_request() -> #hello_request{}. %%-------------------------------------------------------------------- --spec server_hello(#session{}, ssl_record:ssl_version(), #connection_states{}, +-spec server_hello(#session{}, ssl_record:ssl_version(), ssl_record:connection_states(), #hello_extensions{}) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- server_hello(SessionId, Version, ConnectionStates, Extensions) -> - Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates, read), #server_hello{server_version = Version, cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = @@ -335,9 +334,8 @@ verify_server_key(#server_key_params{params_bin = EncParams, signature = Signature}, HashSign = {HashAlgo, _}, ConnectionStates, Version, PubKeyInfo) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, Hash = server_key_exchange_hash(HashAlgo, @@ -447,7 +445,7 @@ init_handshake_history() -> {[], []}. %%-------------------------------------------------------------------- --spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term()) -> +-spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term(), boolean()) -> ssl_handshake:ssl_handshake_history(). %% %% Description: Update the handshake history buffer with Data. @@ -457,14 +455,14 @@ update_handshake_history(Handshake, % special-case SSL2 client hello ?UINT16(CSLength), ?UINT16(0), ?UINT16(CDLength), CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> + ChallengeData:CDLength/binary>>, true) -> update_handshake_history(Handshake, <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor), ?UINT16(CSLength), ?UINT16(0), ?UINT16(CDLength), CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>); -update_handshake_history({Handshake0, _Prev}, Data) -> + ChallengeData:CDLength/binary>>, true); +update_handshake_history({Handshake0, _Prev}, Data, _) -> {[Data|Handshake0], Handshake0}. %% %%-------------------------------------------------------------------- @@ -581,7 +579,7 @@ prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {atom(), atom()} | undefined | #alert{}. %% -%% Description: Handles signature_algorithms extension +%% Description: Handles signature_algorithms hello extension (server) %%-------------------------------------------------------------------- select_hashsign(_, undefined, _, _, _Version) -> {null, anon}; @@ -593,14 +591,17 @@ select_hashsign(HashSigns, Cert, KeyExAlgo, select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - Sign = cert_sign(Algo), - case lists:filter(fun({sha, dsa = S}) when S == Sign -> - true; - ({_, dsa}) -> - false; - ({_, _} = Algos) -> - is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns); + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSing = sign_algo(SubjAlgo), + + case lists:filter(fun({_, S} = Algos) when S == Sign -> + is_acceptable_hash_sign(Algos, Sign, + SubSing, KeyExAlgo, SupportedHashSigns); (_) -> false end, HashSigns) of @@ -613,6 +614,49 @@ select_hashsign(_, Cert, _, _, Version) -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, select_hashsign_algs(undefined, Algo, Version). +%%-------------------------------------------------------------------- +-spec select_hashsign(#certificate_request{}, binary(), + [atom()], ssl_record:ssl_version()) -> + {atom(), atom()} | #alert{}. + +%% +%% Description: Handles signature algorithms selection for certificate requests (client) +%%-------------------------------------------------------------------- +select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + %% There client does not have a certificate and will send an empty reply, the server may fail + %% or accept the connection by its own preference. No signature algorihms needed as there is + %% no certificate to verify. + {undefined, undefined}; + +select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns}, + certificate_types = Types}, Cert, SupportedHashSigns, + {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSign = sign_algo(SubjAlgo), + + case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of + true -> + case lists:filter(fun({_, S} = Algos) when S == SubSign -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); + (_) -> + false + end, HashSigns) of + [] -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); + [HashSign | _] -> + HashSign + end; + false -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) + end; +select_hashsign(#certificate_request{}, Cert, _, Version) -> + select_hashsign(undefined, Cert, undefined, [], Version). %%-------------------------------------------------------------------- -spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> @@ -648,34 +692,34 @@ select_hashsign_algs(undefined, ?rsaEncryption, _) -> select_hashsign_algs(undefined, ?'id-dsa', _) -> {sha, dsa}. + %%-------------------------------------------------------------------- --spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{}, - client | server) -> {binary(), #connection_states{}} | #alert{}. +-spec master_secret(ssl_record:ssl_version(), #session{} | binary(), ssl_record:connection_states(), + client | server) -> {binary(), ssl_record:connection_states()} | #alert{}. %% %% Description: Sets or calculates the master secret and calculate keys, %% updating the pending connection states. The Mastersecret and the update %% connection states are returned or an alert if the calculation fails. %%------------------------------------------------------------------- -master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, +master_secret(Version, #session{master_secret = Mastersecret}, ConnectionStates, Role) -> - ConnectionState = + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - try master_secret(RecordCB, Version, Mastersecret, SecParams, + try master_secret(Version, Mastersecret, SecParams, ConnectionStates, Role) catch exit:_ -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) end; -master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> - ConnectionState = +master_secret(Version, PremasterSecret, ConnectionStates, Role) -> + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{prf_algorithm = PrfAlgo, client_random = ClientRandom, server_random = ServerRandom} = SecParams, - try master_secret(RecordCB, Version, + try master_secret(Version, calc_master_secret(Version,PrfAlgo,PremasterSecret, ClientRandom, ServerRandom), SecParams, ConnectionStates, Role) @@ -1143,11 +1187,13 @@ certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> end; certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa; + KeyExchange == dh_rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa -> <<?BYTE(?RSA_SIGN)>>; -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss; +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss; + KeyExchange == dhe_dss; KeyExchange == srp_dss -> <<?BYTE(?DSS_SIGN)>>; @@ -1170,13 +1216,18 @@ certificate_authorities(CertDbHandle, CertDbRef) -> end, list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). -certificate_authorities_from_db(CertDbHandle, CertDbRef) -> +certificate_authorities_from_db(CertDbHandle, CertDbRef) when is_reference(CertDbRef) -> ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> [Cert | Acc]; (_, Acc) -> Acc end, - ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). + ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle); +certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) -> + %% Cache disabled, Ref contains data + lists:foldl(fun({decoded, {_Key,Cert}}, Acc) -> [Cert | Acc] end, + [], CertDbData). + %%-------------Extension handling -------------------------------- @@ -1294,29 +1345,29 @@ do_select_version( renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; renegotiation_info(_RecordCB, server, ConnectionStates, false) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of true -> #renegotiation_info{renegotiated_connection = ?byte(0)}; false -> #renegotiation_info{renegotiated_connection = undefined} end; renegotiation_info(_RecordCB, client, ConnectionStates, true) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of true -> - Data = CS#connection_state.client_verify_data, + Data = maps:get(client_verify_data, ConnectionState), #renegotiation_info{renegotiated_connection = Data}; false -> #renegotiation_info{renegotiated_connection = undefined} end; renegotiation_info(_RecordCB, server, ConnectionStates, true) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case maps:get(secure_renegotiation, ConnectionState) of true -> - CData = CS#connection_state.client_verify_data, - SData =CS#connection_state.server_verify_data, + CData = maps:get(client_verify_data, ConnectionState), + SData = maps:get(server_verify_data, ConnectionState), #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; false -> #renegotiation_info{renegotiated_connection = undefined} @@ -1339,9 +1390,9 @@ handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, ConnectionStates, true, _, _) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - CData = CS#connection_state.client_verify_data, - SData = CS#connection_state.server_verify_data, + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + CData = maps:get(client_verify_data, ConnectionState), + SData = maps:get(server_verify_data, ConnectionState), case <<CData/binary, SData/binary>> == ClientServerVerify of true -> {ok, ConnectionStates}; @@ -1355,8 +1406,8 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co true -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); false -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - Data = CS#connection_state.client_verify_data, + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + Data = maps:get(client_verify_data, ConnectionState), case Data == ClientVerify of true -> {ok, ConnectionStates}; @@ -1377,8 +1428,8 @@ handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, S end. handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> - CS = ssl_record:current_connection_state(ConnectionStates, read), - case {SecureRenegotation, CS#connection_state.secure_renegotiation} of + ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), + case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of {_, true} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); {true, false} -> @@ -1596,7 +1647,7 @@ calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). -master_secret(_RecordCB, Version, MasterSecret, +master_secret(Version, MasterSecret, #security_parameters{ bulk_cipher_algorithm = BCA, client_random = ClientRandom, @@ -1679,18 +1730,16 @@ hello_pending_connection_states(_RecordCB, Role, Version, CipherSuite, Random, C NewWriteSecParams, ConnectionStates). -hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, +hello_security_parameters(client, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ server_random = Random, compression_algorithm = Compression }; -hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, +hello_security_parameters(server, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ client_random = Random, @@ -2196,27 +2245,73 @@ distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) -> [{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs] end. -cert_sign(?rsaEncryption) -> +sign_algo(?rsaEncryption) -> rsa; -cert_sign(?'id-ecPublicKey') -> +sign_algo(?'id-ecPublicKey') -> ecdsa; -cert_sign(?'id-dsa') -> +sign_algo(?'id-dsa') -> dsa; -cert_sign(Alg) -> +sign_algo(Alg) -> {_, Sign} =public_key:pkix_sign_types(Alg), Sign. -is_acceptable_hash_sign({_, Sign} = Algos, Sign, _, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(Algos,_, KeyExAlgo, SupportedHashSigns) when KeyExAlgo == dh_ecdsa; - KeyExAlgo == ecdh_rsa; - KeyExAlgo == ecdh_ecdsa -> +is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when + KeyExAlgo == dh_dss; + KeyExAlgo == dh_rsa; + KeyExAlgo == dh_ecdsa -> + %% dh_* could be called only dh in TLS-1.2 + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) -> is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(_,_,_,_) -> - false. +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when + KeyExAlgo == psk; + KeyExAlgo == dhe_psk; + KeyExAlgo == srp_anon; + KeyExAlgo == dh_anon; + KeyExAlgo == ecdhe_anon + -> + true; +is_acceptable_hash_sign(_,_, _,_,_) -> + false. + is_acceptable_hash_sign(Algos, SupportedHashSigns) -> lists:member(Algos, SupportedHashSigns). +is_acceptable_cert_type(Sign, _HashSigns, Types) -> + lists:member(sign_type(Sign), binary_to_list(Types)). + +is_supported_sign(Sign, HashSigns) -> + [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign -> + true; + (_)-> + false + end, HashSigns). +sign_type(rsa) -> + ?RSA_SIGN; +sign_type(dsa) -> + ?DSS_SIGN; +sign_type(ecdsa) -> + ?ECDSA_SIGN. + + bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; bad_key(#'RSAPrivateKey'{}) -> diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index c7dcbaabe9..5bd9521de7 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -115,13 +115,25 @@ start_link_dist(Opts) -> %% Description: Do necessary initializations for a new connection. %%-------------------------------------------------------------------- connection_init({der, _} = Trustedcerts, Role, CRLCache) -> - call({connection_init, Trustedcerts, Role, CRLCache}); + case bypass_pem_cache() of + true -> + {ok, Extracted} = ssl_pkix_db:extract_trusted_certs(Trustedcerts), + call({connection_init, Extracted, Role, CRLCache}); + false -> + call({connection_init, Trustedcerts, Role, CRLCache}) + end; connection_init(<<>> = Trustedcerts, Role, CRLCache) -> call({connection_init, Trustedcerts, Role, CRLCache}); connection_init(Trustedcerts, Role, CRLCache) -> - call({connection_init, Trustedcerts, Role, CRLCache}). + case bypass_pem_cache() of + true -> + {ok, Extracted} = ssl_pkix_db:extract_trusted_certs(Trustedcerts), + call({connection_init, Extracted, Role, CRLCache}); + false -> + call({connection_init, Trustedcerts, Role, CRLCache}) + end. %%-------------------------------------------------------------------- -spec cache_pem_file(binary(), term()) -> {ok, term()} | {error, reason()}. @@ -129,13 +141,18 @@ connection_init(Trustedcerts, Role, CRLCache) -> %% Description: Cache a pem file and return its content. %%-------------------------------------------------------------------- cache_pem_file(File, DbHandle) -> - case ssl_pkix_db:lookup_cached_pem(DbHandle, File) of - [{Content,_}] -> - {ok, Content}; - [Content] -> - {ok, Content}; - undefined -> - call({cache_pem, File}) + case bypass_pem_cache() of + true -> + ssl_pkix_db:decode_pem_file(File); + false -> + case ssl_pkix_db:lookup_cached_pem(DbHandle, File) of + [{Content,_}] -> + {ok, Content}; + [Content] -> + {ok, Content}; + undefined -> + call({cache_pem, File}) + end end. %%-------------------------------------------------------------------- @@ -506,6 +523,14 @@ delay_time() -> ?CLEAN_SESSION_DB end. +bypass_pem_cache() -> + case application:get_env(ssl, bypass_pem_cache) of + {ok, Bool} when is_boolean(Bool) -> + Bool; + _ -> + false + end. + max_session_cache_size(CacheType) -> case application:get_env(ssl, CacheType) of {ok, Size} when is_integer(Size) -> diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index b16903d7c7..0006ce14d9 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -29,10 +29,11 @@ -include_lib("kernel/include/file.hrl"). -export([create/0, add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, + extract_trusted_certs/1, remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2, lookup_cached_pem/2, cache_pem_file/2, cache_pem_file/3, - lookup/2]). + decode_pem_file/1, lookup/2]). %%==================================================================== %% Internal application API @@ -82,12 +83,22 @@ remove(Dbs) -> %% <SerialNumber, Issuer>. Ref is used as it is specified %% for each connection which certificates are trusted. %%-------------------------------------------------------------------- -lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> +lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) when is_reference(Ref) -> case lookup({Ref, SerialNumber, Issuer}, DbHandle) of undefined -> undefined; [Certs] -> {ok, Certs} + end; +lookup_trusted_cert(_DbHandle, {extracted,Certs}, SerialNumber, Issuer) -> + try + [throw(Cert) + || {decoded, {{_Ref,CertSerial,CertIssuer}, Cert}} <- Certs, + CertSerial =:= SerialNumber, CertIssuer =:= Issuer], + undefined + catch + Cert -> + {ok, Cert} end. lookup_cached_pem([_, _, PemChache | _], File) -> @@ -103,6 +114,9 @@ lookup_cached_pem(PemChache, File) -> %% runtime database. Returns Ref that should be handed to lookup_trusted_cert %% together with the cert serialnumber and issuer. %%-------------------------------------------------------------------- +add_trusted_certs(_Pid, {extracted, _} = Certs, _) -> + {ok, Certs}; + add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) -> NewRef = make_ref(), add_certs_from_der(DerList, NewRef, CertDb), @@ -122,6 +136,21 @@ add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache | _] = Db) -> undefined -> new_trusted_cert_entry(File, Db) end. + +extract_trusted_certs({der, DerList}) -> + {ok, {extracted, certs_from_der(DerList)}}; +extract_trusted_certs(File) -> + case file:read_file(File) of + {ok, PemBin} -> + Content = public_key:pem_decode(PemBin), + DerList = [Cert || {'Certificate', Cert, not_encrypted} <- Content], + {ok, {extracted, certs_from_der(DerList)}}; + Error -> + %% Have to simulate a failure happening in a server for + %% external handlers. + {error, {badmatch, Error}} + end. + %%-------------------------------------------------------------------- %% %% Description: Cache file as binary in DB @@ -141,6 +170,18 @@ cache_pem_file(Ref, File, [_CertsDb, _RefDb, PemChache| _]) -> insert(File, {Content, Ref}, PemChache), {ok, Content}. +-spec decode_pem_file(binary()) -> {ok, term()}. +decode_pem_file(File) -> + case file:read_file(File) of + {ok, PemBin} -> + Content = public_key:pem_decode(PemBin), + {ok, Content}; + Error -> + %% Have to simulate a failure happening in a server for + %% external handlers. + {error, {badmatch, Error}} + end. + %%-------------------------------------------------------------------- -spec remove_trusted_certs(reference(), db_handle()) -> ok. %% @@ -203,6 +244,8 @@ select_cert_by_issuer(Cache, Issuer) -> %% %% Description: Updates a reference counter in a <Db>. %%-------------------------------------------------------------------- +ref_count({extracted, _}, _Db, _N) -> + not_cached; ref_count(Key, Db, N) -> ets:update_counter(Db,Key,N). @@ -248,23 +291,39 @@ add_certs_from_der(DerList, Ref, CertsDb) -> [Add(Cert) || Cert <- DerList], ok. +certs_from_der(DerList) -> + Ref = make_ref(), + [Decoded || Cert <- DerList, + Decoded <- [decode_certs(Ref, Cert)], + Decoded =/= undefined]. + add_certs_from_pem(PemEntries, Ref, CertsDb) -> Add = fun(Cert) -> add_certs(Cert, Ref, CertsDb) end, [Add(Cert) || {'Certificate', Cert, not_encrypted} <- PemEntries], ok. add_certs(Cert, Ref, CertsDb) -> + try + {decoded, {Key, Val}} = decode_certs(Ref, Cert), + insert(Key, Val, CertsDb) + catch + error:_ -> + ok + end. + +decode_certs(Ref, Cert) -> try ErlCert = public_key:pkix_decode_cert(Cert, otp), TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate, SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber, Issuer = public_key:pkix_normalize_name( TBSCertificate#'OTPTBSCertificate'.issuer), - insert({Ref, SerialNumber, Issuer}, {Cert,ErlCert}, CertsDb) + {decoded, {{Ref, SerialNumber, Issuer}, {Cert, ErlCert}}} catch error:_ -> Report = io_lib:format("SSL WARNING: Ignoring a CA cert as " "it could not be correctly decoded.~n", []), - error_logger:info_report(Report) + error_logger:info_report(Report), + undefined end. new_trusted_cert_entry(File, [CertsDb, RefDb, _ | _] = Db) -> diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 5bb1c92c2d..71cd0279f3 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -30,8 +30,7 @@ -include("ssl_alert.hrl"). %% Connection state handling --export([init_connection_states/2, - current_connection_state/2, pending_connection_state/2, +-export([initial_security_params/1, current_connection_state/2, pending_connection_state/2, activate_pending_connection_state/2, set_security_params/3, set_mac_secret/4, @@ -39,7 +38,8 @@ set_pending_cipher_state/4, set_renegotiation_flag/2, set_client_verify_data/3, - set_server_verify_data/3]). + set_server_verify_data/3, + empty_connection_state/2, initial_connection_state/2, record_protocol_role/1]). %% Encoding records -export([encode_handshake/3, encode_alert_record/3, @@ -52,122 +52,92 @@ -export([cipher/4, decipher/4, is_correct_mac/2, cipher_aead/4, decipher_aead/4]). --export_type([ssl_version/0, ssl_atom_version/0]). +-export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]). -type ssl_version() :: {integer(), integer()}. -type ssl_atom_version() :: tls_record:tls_atom_version(). - +-type connection_states() :: term(). %% Map +-type connection_state() :: term(). %% Map %%==================================================================== %% Internal application API %%==================================================================== + %%-------------------------------------------------------------------- --spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled ) -> - #connection_states{}. -%% -%% Description: Creates a connection_states record with appropriate -%% values for the initial SSL connection setup. -%%-------------------------------------------------------------------- -init_connection_states(Role, BeastMitigation) -> - ConnectionEnd = record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd, BeastMitigation), - Pending = empty_connection_state(ConnectionEnd, BeastMitigation), - #connection_states{dtls_write_msg_seq = 1, % only used by dtls - current_read = Current, - pending_read = Pending, - current_write = Current, - pending_write = Pending - }. - -%%-------------------------------------------------------------------- --spec current_connection_state(#connection_states{}, read | write) -> - #connection_state{}. +-spec current_connection_state(connection_states(), read | write) -> + connection_state(). %% -%% Description: Returns the instance of the connection_state record +%% Description: Returns the instance of the connection_state map %% that is currently defined as the current conection state. %%-------------------------------------------------------------------- -current_connection_state(#connection_states{current_read = Current}, - read) -> - Current; -current_connection_state(#connection_states{current_write = Current}, - write) -> - Current. +current_connection_state(ConnectionStates, read) -> + maps:get(current_read, ConnectionStates); +current_connection_state(ConnectionStates, write) -> + maps:get(current_write, ConnectionStates). %%-------------------------------------------------------------------- --spec pending_connection_state(#connection_states{}, read | write) -> - term(). +-spec pending_connection_state(connection_states(), read | write) -> + connection_state(). %% -%% Description: Returns the instance of the connection_state record -%% that is currently defined as the pending conection state. +%% Description: Returns the instance of the connection_state map +%% that is pendingly defined as the pending conection state. %%-------------------------------------------------------------------- -pending_connection_state(#connection_states{pending_read = Pending}, - read) -> - Pending; -pending_connection_state(#connection_states{pending_write = Pending}, - write) -> - Pending. - +pending_connection_state(ConnectionStates, read) -> + maps:get(pending_read, ConnectionStates); +pending_connection_state(ConnectionStates, write) -> + maps:get(pending_write, ConnectionStates). %%-------------------------------------------------------------------- --spec activate_pending_connection_state(#connection_states{}, read | write) -> - #connection_states{}. +-spec activate_pending_connection_state(connection_states(), read | write) -> + connection_states(). %% %% Description: Creates a new instance of the connection_states record %% where the pending state of <Type> has been activated. %%-------------------------------------------------------------------- -activate_pending_connection_state(States = - #connection_states{current_read = Current, - pending_read = Pending}, +activate_pending_connection_state(#{current_read := Current, + pending_read := Pending} = States, read) -> - NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), - sequence_number = 0}, - BeastMitigation = Pending#connection_state.beast_mitigation, - SecParams = Pending#connection_state.security_parameters, + #{secure_renegotiation := SecureRenegotation} = Current, + #{beast_mitigation := BeastMitigation, + security_parameters := SecParams} = Pending, + NewCurrent = Pending#{sequence_number => 0}, ConnectionEnd = SecParams#security_parameters.connection_end, EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_read = NewCurrent, - pending_read = NewPending - }; - -activate_pending_connection_state(States = - #connection_states{current_write = Current, - pending_write = Pending}, + NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation}, + States#{current_read => NewCurrent, + pending_read => NewPending + }; + +activate_pending_connection_state(#{current_write := Current, + pending_write := Pending} = States, write) -> - NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), - sequence_number = 0}, - BeastMitigation = Pending#connection_state.beast_mitigation, - SecParams = Pending#connection_state.security_parameters, + NewCurrent = Pending#{sequence_number => 0}, + #{secure_renegotiation := SecureRenegotation} = Current, + #{beast_mitigation := BeastMitigation, + security_parameters := SecParams} = Pending, ConnectionEnd = SecParams#security_parameters.connection_end, EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_write = NewCurrent, - pending_write = NewPending - }. - + NewPending = EmptyPending#{secure_renegotiation => SecureRenegotation}, + States#{current_write => NewCurrent, + pending_write => NewPending + }. %%-------------------------------------------------------------------- -spec set_security_params(#security_parameters{}, #security_parameters{}, - #connection_states{}) -> #connection_states{}. + connection_states()) -> connection_states(). %% %% Description: Creates a new instance of the connection_states record %% where the pending states gets its security parameters updated. %%-------------------------------------------------------------------- -set_security_params(ReadParams, WriteParams, States = - #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{pending_read = - Read#connection_state{security_parameters = - ReadParams}, - pending_write = - Write#connection_state{security_parameters = - WriteParams} - }. +set_security_params(ReadParams, WriteParams, + #{pending_read := Read, + pending_write := Write} = States) -> + States#{pending_read => Read#{security_parameters => ReadParams}, + pending_write => Write#{security_parameters => WriteParams} + }. %%-------------------------------------------------------------------- -spec set_mac_secret(binary(), binary(), client | server, - #connection_states{}) -> #connection_states{}. + connection_states()) -> connection_states(). %% %% Description: update the mac_secret field in pending connection states %%-------------------------------------------------------------------- @@ -177,152 +147,145 @@ set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). set_mac_secret(ReadMacSecret, WriteMacSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{ - pending_read = Read#connection_state{mac_secret = ReadMacSecret}, - pending_write = Write#connection_state{mac_secret = WriteMacSecret} + States = #{pending_read := Read, + pending_write := Write}) -> + States#{pending_read => Read#{mac_secret => ReadMacSecret}, + pending_write => Write#{mac_secret => WriteMacSecret} }. %%-------------------------------------------------------------------- --spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. +-spec set_master_secret(binary(), connection_states()) -> connection_states(). %% %% Description: Set master_secret in pending connection states %%-------------------------------------------------------------------- set_master_secret(MasterSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - ReadSecPar = Read#connection_state.security_parameters, - Read1 = Read#connection_state{ - security_parameters = ReadSecPar#security_parameters{ - master_secret = MasterSecret}}, - WriteSecPar = Write#connection_state.security_parameters, - Write1 = Write#connection_state{ - security_parameters = WriteSecPar#security_parameters{ - master_secret = MasterSecret}}, - States#connection_states{pending_read = Read1, pending_write = Write1}. - -%%-------------------------------------------------------------------- --spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. + States = #{pending_read := Read = #{security_parameters := ReadSecPar}, + pending_write := Write = #{security_parameters := WriteSecPar}}) -> + Read1 = Read#{security_parameters => ReadSecPar#security_parameters{ + master_secret = MasterSecret}}, + Write1 = Write#{security_parameters => WriteSecPar#security_parameters{ + master_secret = MasterSecret}}, + States#{pending_read => Read1, pending_write => Write1}. + +%%-------------------------------------------------------------------- +-spec set_renegotiation_flag(boolean(), connection_states()) -> connection_states(). %% %% Description: Set secure_renegotiation in pending connection states %%-------------------------------------------------------------------- -set_renegotiation_flag(Flag, #connection_states{ - current_read = CurrentRead0, - current_write = CurrentWrite0, - pending_read = PendingRead0, - pending_write = PendingWrite0} +set_renegotiation_flag(Flag, #{current_read := CurrentRead0, + current_write := CurrentWrite0, + pending_read := PendingRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, - CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, - PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, - PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite, - pending_read = PendingRead, - pending_write = PendingWrite}. + CurrentRead = CurrentRead0#{secure_renegotiation => Flag}, + CurrentWrite = CurrentWrite0#{secure_renegotiation => Flag}, + PendingRead = PendingRead0#{secure_renegotiation => Flag}, + PendingWrite = PendingWrite0#{secure_renegotiation => Flag}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite, + pending_read => PendingRead, + pending_write => PendingWrite}. %%-------------------------------------------------------------------- -spec set_client_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. + binary(), connection_states())-> + connection_states(). %% %% Description: Set verify data in connection states. %%-------------------------------------------------------------------- set_client_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + #{current_read := CurrentRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; + CurrentRead = CurrentRead0#{client_verify_data => Data}, + PendingWrite = PendingWrite0#{client_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + pending_write => PendingWrite}; set_client_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + #{pending_read := PendingRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; + PendingRead = PendingRead0#{client_verify_data => Data}, + CurrentWrite = CurrentWrite0#{client_verify_data => Data}, + ConnectionStates#{pending_read => PendingRead, + current_write => CurrentWrite}; set_client_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + #{current_read := CurrentRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. + CurrentRead = CurrentRead0#{client_verify_data => Data}, + CurrentWrite = CurrentWrite0#{client_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite}. %%-------------------------------------------------------------------- -spec set_server_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. + binary(), connection_states())-> + connection_states(). %% %% Description: Set verify data in pending connection states. %%-------------------------------------------------------------------- set_server_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} + #{pending_read := PendingRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; + PendingRead = PendingRead0#{server_verify_data => Data}, + CurrentWrite = CurrentWrite0#{server_verify_data => Data}, + ConnectionStates#{pending_read => PendingRead, + current_write => CurrentWrite}; set_server_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} + #{current_read := CurrentRead0, + pending_write := PendingWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; + CurrentRead = CurrentRead0#{server_verify_data => Data}, + PendingWrite = PendingWrite0#{server_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + pending_write => PendingWrite}; set_server_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} + #{current_read := CurrentRead0, + current_write := CurrentWrite0} = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. + CurrentRead = CurrentRead0#{server_verify_data => Data}, + CurrentWrite = CurrentWrite0#{server_verify_data => Data}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite}. %%-------------------------------------------------------------------- --spec set_pending_cipher_state(#connection_states{}, #cipher_state{}, +-spec set_pending_cipher_state(connection_states(), #cipher_state{}, #cipher_state{}, client | server) -> - #connection_states{}. + connection_states(). %% %% Description: Set the cipher state in the specified pending connection state. %%-------------------------------------------------------------------- -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, +set_pending_cipher_state(#{pending_read := Read, + pending_write := Write} = States, ClientState, ServerState, server) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ClientState}, - pending_write = Write#connection_state{cipher_state = ServerState}}; + States#{ + pending_read => Read#{cipher_state => ClientState}, + pending_write => Write#{cipher_state => ServerState}}; -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, +set_pending_cipher_state(#{pending_read := Read, + pending_write := Write} = States, ClientState, ServerState, client) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ServerState}, - pending_write = Write#connection_state{cipher_state = ClientState}}. + States#{ + pending_read => Read#{cipher_state => ServerState}, + pending_write => Write#{cipher_state => ClientState}}. %%-------------------------------------------------------------------- --spec encode_handshake(iolist(), ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_handshake(iolist(), ssl_version(), connection_states()) -> + {iolist(), connection_states()}. %% %% Description: Encodes a handshake message to send on the ssl-socket. %%-------------------------------------------------------------------- encode_handshake(Frag, Version, - #connection_states{current_write = - #connection_state{ - beast_mitigation = BeastMitigation, - security_parameters = - #security_parameters{bulk_cipher_algorithm = BCA}}} = + #{current_write := + #{beast_mitigation := BeastMitigation, + security_parameters := + #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -when is_list(Frag) -> + when is_list(Frag) -> case iolist_size(Frag) of N when N > ?MAX_PLAIN_TEXT_LENGTH -> Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), @@ -341,8 +304,8 @@ encode_handshake(Frag, Version, ConnectionStates) -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). %%-------------------------------------------------------------------- --spec encode_alert_record(#alert{}, ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_alert_record(#alert{}, ssl_version(), connection_states()) -> + {iolist(), connection_states()}. %% %% Description: Encodes an alert message to send on the ssl-socket. %%-------------------------------------------------------------------- @@ -352,8 +315,8 @@ encode_alert_record(#alert{level = Level, description = Description}, ConnectionStates). %%-------------------------------------------------------------------- --spec encode_change_cipher_spec(ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_change_cipher_spec(ssl_version(), connection_states()) -> + {iolist(), connection_states()}. %% %% Description: Encodes a change_cipher_spec-message to send on the ssl socket. %%-------------------------------------------------------------------- @@ -361,15 +324,14 @@ encode_change_cipher_spec(Version, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). %%-------------------------------------------------------------------- --spec encode_data(binary(), ssl_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. +-spec encode_data(binary(), ssl_version(), connection_states()) -> + {iolist(), connection_states()}. %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- encode_data(Frag, Version, - #connection_states{current_write = #connection_state{ - beast_mitigation = BeastMitigation, - security_parameters = + #{current_write := #{beast_mitigation := BeastMitigation, + security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), @@ -390,73 +352,74 @@ compressions() -> [?byte(?NULL)]. %%-------------------------------------------------------------------- --spec cipher(ssl_version(), iodata(), #connection_state{}, MacHash::binary()) -> - {CipherFragment::binary(), #connection_state{}}. +-spec cipher(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> + {CipherFragment::binary(), connection_state()}. %% %% Description: Payload encryption %%-------------------------------------------------------------------- cipher(Version, Fragment, - #connection_state{cipher_state = CipherS0, - security_parameters= - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo} - } = WriteState0, MacHash) -> - + #{cipher_state := CipherS0, + security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, MacHash) -> + {CipherFragment, CipherS1} = ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), - {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- --spec cipher_aead(ssl_version(), iodata(), #connection_state{}, MacHash::binary()) -> - {CipherFragment::binary(), #connection_state{}}. +-spec cipher_aead(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> + {CipherFragment::binary(), connection_state()}. %% %% Description: Payload encryption %%-------------------------------------------------------------------- cipher_aead(Version, Fragment, - #connection_state{cipher_state = CipherS0, - sequence_number = SeqNo, - security_parameters= - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo} - } = WriteState0, AAD) -> - + #{cipher_state := CipherS0, + sequence_number := SeqNo, + security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, AAD) -> + {CipherFragment, CipherS1} = ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, Fragment, Version), - {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. + {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- --spec decipher(ssl_version(), binary(), #connection_state{}, boolean()) -> {binary(), binary(), #connection_state{}} | #alert{}. +-spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> {binary(), binary(), connection_state} | #alert{}. %% %% Description: Payload decryption %%-------------------------------------------------------------------- decipher(Version, CipherFragment, - #connection_state{security_parameters = - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo, - hash_size = HashSz}, - cipher_state = CipherS0 - } = ReadState, PaddingCheck) -> + #{security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo, + hash_size = HashSz}, + cipher_state := CipherS0 + } = ReadState, PaddingCheck) -> case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version, PaddingCheck) of {PlainFragment, Mac, CipherS1} -> - CS1 = ReadState#connection_state{cipher_state = CipherS1}, + CS1 = ReadState#{cipher_state => CipherS1}, {PlainFragment, Mac, CS1}; #alert{} = Alert -> Alert end. %%-------------------------------------------------------------------- --spec decipher_aead(ssl_version(), binary(), #connection_state{}, binary()) -> {binary(), binary(), #connection_state{}} | #alert{}. +-spec decipher_aead(ssl_version(), binary(), connection_state(), binary()) -> + {binary(), binary(), connection_state()} | #alert{}. %% %% Description: Payload decryption %%-------------------------------------------------------------------- decipher_aead(Version, CipherFragment, - #connection_state{sequence_number = SeqNo, - security_parameters = - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo}, - cipher_state = CipherS0 - } = ReadState, AAD) -> + #{sequence_number := SeqNo, + security_parameters := + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo}, + cipher_state := CipherS0 + } = ReadState, AAD) -> case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, CipherFragment, Version) of {PlainFragment, CipherS1} -> - CS1 = ReadState#connection_state{cipher_state = CipherS1}, + CS1 = ReadState#{cipher_state => CipherS1}, {PlainFragment, CS1}; #alert{} = Alert -> Alert @@ -466,8 +429,15 @@ decipher_aead(Version, CipherFragment, %%-------------------------------------------------------------------- empty_connection_state(ConnectionEnd, BeastMitigation) -> SecParams = empty_security_params(ConnectionEnd), - #connection_state{security_parameters = SecParams, - beast_mitigation = BeastMitigation}. + #{security_parameters => SecParams, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. empty_security_params(ConnectionEnd = ?CLIENT) -> #security_parameters{connection_end = ConnectionEnd, @@ -481,10 +451,10 @@ random() -> Random_28_bytes = ssl_cipher:random_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. -dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS - undefined; -dtls_next_epoch(#connection_state{epoch = Epoch}) -> %% DTLS - Epoch + 1. +%% dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS +%% undefined; +%% dtls_next_epoch(#connection_state{epoch = Epoch}) -> %% DTLS +%% Epoch + 1. is_correct_mac(Mac, Mac) -> true; @@ -497,11 +467,17 @@ record_protocol_role(server) -> ?SERVER. initial_connection_state(ConnectionEnd, BeastMitigation) -> - #connection_state{security_parameters = - initial_security_params(ConnectionEnd), - sequence_number = 0, - beast_mitigation = BeastMitigation - }. + #{security_parameters => + initial_security_params(ConnectionEnd), + sequence_number => 0, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. initial_security_params(ConnectionEnd) -> SecParams = #security_parameters{connection_end = ConnectionEnd, diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index a41264ff9b..ed007f58d7 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -30,29 +30,27 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Connection states - RFC 4346 section 6.1 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --record(connection_state, { - security_parameters, - compression_state, - cipher_state, - mac_secret, - epoch, %% Only used by DTLS - sequence_number, - %% RFC 5746 - secure_renegotiation, - client_verify_data, - server_verify_data, - %% How to do BEAST mitigation? - beast_mitigation - }). - --record(connection_states, { - dtls_write_msg_seq, %% Only used by DTLS +%% For documentation purposes are now maps in implementation +%% -record(connection_state, { +%% security_parameters, +%% compression_state, +%% cipher_state, +%% mac_secret, +%% sequence_number, +%% %% RFC 5746 +%% secure_renegotiation, +%% client_verify_data, +%% server_verify_data, +%% %% How to do BEAST mitigation? +%% beast_mitigation +%% }). - current_read, - pending_read, - current_write, - pending_write - }). +%% -record(connection_states, { +%% current_read, +%% pending_read, +%% current_write, +%% pending_write, +%% }). -record(security_parameters, { cipher_suite, diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index 7fa1f7dc9e..ba20f65f44 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -47,11 +47,13 @@ init([]) -> SessionCertManager = session_and_cert_manager_child_spec(), TLSConnetionManager = tls_connection_manager_child_spec(), %% Not supported yet - %%DTLSConnetionManager = tls_connection_manager_child_spec(), + %%DTLSConnetionManager = dtls_connection_manager_child_spec(), %% Handles emulated options so that they inherited by the accept socket, even when setopts is performed on %% the listen socket ListenOptionsTracker = listen_options_tracker_child_spec(), - {ok, {{one_for_all, 10, 3600}, [SessionCertManager, TLSConnetionManager, ListenOptionsTracker]}}. + {ok, {{one_for_all, 10, 3600}, [SessionCertManager, TLSConnetionManager, + %%DTLSConnetionManager, + ListenOptionsTracker]}}. manager_opts() -> @@ -93,15 +95,14 @@ tls_connection_manager_child_spec() -> {Name, StartFunc, Restart, Shutdown, Type, Modules}. %% dtls_connection_manager_child_spec() -> -%% Name = dtls_connection, +%% Name = dtls_connection, %% StartFunc = {dtls_connection_sup, start_link, []}, -%% Restart = permanent, +%% Restart = permanent, %% Shutdown = 4000, %% Modules = [dtls_connection, ssl_connection], %% Type = supervisor, %% {Name, StartFunc, Restart, Shutdown, Type, Modules}. - listen_options_tracker_child_spec() -> Name = ssl_socket, StartFunc = {ssl_listen_tracker_sup, start_link, []}, diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl index a920f54ed2..08947f24dd 100644 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -117,7 +117,7 @@ handle_call({listen, Driver, Name}, _From, State) -> {ok, WorldTcpAddress} = get_tcp_address(World), {_,Port} = WorldTcpAddress#net_address.address, ErlEpmd = net_kernel:epmd_module(), - case ErlEpmd:register_node(Name, Port) of + case ErlEpmd:register_node(Name, Port, Driver) of {ok, Creation} -> {reply, {ok, {Socket, TcpAddress, Creation}}, State#state{listen={Socket, World}}}; diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 9880befa94..9b9031473a 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -51,27 +51,21 @@ %% Handshake handling -export([renegotiate/2, send_handshake/2, queue_handshake/2, queue_change_cipher/2, - reinit_handshake_data/1, handle_sni_extension/2]). + reinit_handshake_data/1, select_sni_extension/1]). %% Alert and close handling --export([send_alert/2, handle_own_alert/4, handle_close_alert/3, - handle_normal_shutdown/3, - close/5, alert_user/6, alert_user/9 - ]). +-export([send_alert/2, close/5]). %% Data handling --export([write_application_data/3, read_application_data/2, - passive_receive/2, next_record_if_active/1, handle_common_event/4]). +-export([passive_receive/2, next_record_if_active/1, handle_common_event/4]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states connection/3]). %% gen_statem callbacks --export([terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, terminate/3, code_change/4, format_status/2]). --define(GEN_STATEM_CB_MODE, state_functions). - %%==================================================================== %% Internal application API %%==================================================================== @@ -109,9 +103,10 @@ send_handshake(Handshake, State) -> queue_handshake(Handshake, #state{negotiated_version = Version, tls_handshake_history = Hist0, flight_buffer = Flight0, + ssl_options = #ssl_options{v2_hello_compatible = V2HComp}, connection_states = ConnectionStates0} = State0) -> {BinHandshake, ConnectionStates, Hist} = - encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp), State0#state{connection_states = ConnectionStates, tls_handshake_history = Hist, flight_buffer = Flight0 ++ [BinHandshake]}. @@ -149,6 +144,11 @@ reinit_handshake_data(State) -> tls_handshake_history = ssl_handshake:init_handshake_history() }. +select_sni_extension(#client_hello{extensions = HelloExtensions}) -> + HelloExtensions#hello_extensions.sni; +select_sni_extension(_) -> + undefined. + %%==================================================================== %% tls_connection_sup API %%==================================================================== @@ -169,11 +169,14 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + gen_statem:enter_loop(?MODULE, [], init, State) catch throw:Error -> - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error, State0}) + gen_statem:enter_loop(?MODULE, [], error, {Error, State0}) end. +callback_mode() -> + state_functions. + %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- @@ -185,7 +188,7 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> init({call, From}, {start, Timeout}, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, + ssl_options = #ssl_options{v2_hello_compatible = V2HComp} = SslOpts, session = #session{own_certificate = Cert} = Session0, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates0, @@ -201,7 +204,7 @@ init({call, From}, {start, Timeout}, HelloVersion = tls_record:lowest_protocol_version(SslOpts#ssl_options.versions), Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), + encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0, V2HComp), Transport:send(Socket, BinMsg), State1 = State0#state{connection_states = ConnectionStates, negotiated_version = Version, %% Requested version @@ -213,7 +216,7 @@ init({call, From}, {start, Timeout}, {Record, State} = next_record(State1), next_event(hello, Record, State); init(Type, Event, State) -> - ssl_connection:init(Type, Event, State, ?MODULE). + gen_handshake(ssl_connection, init, Type, Event, State). %%-------------------------------------------------------------------- -spec error(gen_statem:event_type(), @@ -249,7 +252,7 @@ hello(internal, #client_hello{client_version = ClientVersion, case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State); + ssl_connection:handle_own_alert(Alert, ClientVersion, hello, State); {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> Protocol = case Protocol0 of @@ -257,13 +260,13 @@ hello(internal, #client_hello{client_version = ClientVersion, _ -> Protocol0 end, - ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, + gen_handshake(ssl_connection, hello, internal, {common_client_hello, Type, ServerHelloExt}, State#state{connection_states = ConnectionStates, negotiated_version = Version, hashsign_algorithm = HashSign, session = Session, client_ecc = {EllipticCurves, EcPointFormats}, - negotiated_protocol = Protocol}, ?MODULE) + negotiated_protocol = Protocol}) end; hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, @@ -273,42 +276,42 @@ hello(internal, #server_hello{} = Hello, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State); + ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State); {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; hello(info, Event, State) -> - handle_info(Event, hello, State); + gen_info(Event, hello, State); hello(Type, Event, State) -> - ssl_connection:hello(Type, Event, State, ?MODULE). + gen_handshake(ssl_connection, hello, Type, Event, State). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- abbreviated(info, Event, State) -> - handle_info(Event, abbreviated, State); + gen_info(Event, abbreviated, State); abbreviated(Type, Event, State) -> - ssl_connection:abbreviated(Type, Event, State, ?MODULE). + gen_handshake(ssl_connection, abbreviated, Type, Event, State). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- certify(info, Event, State) -> - handle_info(Event, certify, State); + gen_info(Event, certify, State); certify(Type, Event, State) -> - ssl_connection:certify(Type, Event, State, ?MODULE). + gen_handshake(ssl_connection, certify, Type, Event, State). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- cipher(info, Event, State) -> - handle_info(Event, cipher, State); + gen_info(Event, cipher, State); cipher(Type, Event, State) -> - ssl_connection:cipher(Type, Event, State, ?MODULE). + gen_handshake(ssl_connection, cipher, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -316,7 +319,7 @@ cipher(Type, Event, State) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection(info, Event, State) -> - handle_info(Event, connection, State); + gen_info(Event, connection, State); connection(internal, #hello_request{}, #state{role = client, host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, @@ -373,7 +376,7 @@ handle_info({Protocol, _, Data}, StateName, {Record, State} -> next_event(StateName, Record, State); #alert{} = Alert -> - handle_normal_shutdown(Alert, StateName, State0), + ssl_connection:handle_normal_shutdown(Alert, StateName, State0), {stop, {shutdown, own_alert}} end; handle_info({CloseTag, Socket}, StateName, @@ -393,14 +396,14 @@ handle_info({CloseTag, Socket}, StateName, %%invalidate_session(Role, Host, Port, Session) ok end, - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), {stop, {shutdown, transport_closed}}; handle_info(Msg, StateName, State) -> ssl_connection:handle_info(Msg, StateName, State). handle_common_event(internal, #alert{} = Alert, StateName, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, StateName, State); + ssl_connection:handle_own_alert(Alert, Version, StateName, State); %%% TLS record protocol level handshake messages handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, @@ -421,7 +424,7 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, {next_state, StateName, State, Events} end catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0) + ssl_connection:handle_own_alert(Alert, Version, StateName, State0) end; %%% TLS record protocol level application data messages handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) -> @@ -432,11 +435,19 @@ handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Da %%% TLS record protocol level Alert messages handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, #state{negotiated_version = Version} = State) -> - case decode_alerts(EncAlerts) of + try decode_alerts(EncAlerts) of Alerts = [_|_] -> handle_alerts(Alerts, {next_state, StateName, State}); + [] -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), + Version, StateName, State); #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State) + ssl_connection:handle_own_alert(Alert, Version, StateName, State) + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), + Version, StateName, State) + end; %% Ignore unknown TLS record level protocol messages handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> @@ -457,16 +468,16 @@ format_status(Type, Data) -> %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State0, {Direction, From, To}) -> State = convert_state(State0, Direction, From, To), - {?GEN_STATEM_CB_MODE, StateName, State}; + {ok, StateName, State}; code_change(_OldVsn, StateName, State, _) -> - {?GEN_STATEM_CB_MODE, StateName, State}. + {ok, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> +encode_handshake(Handshake, Version, ConnectionStates0, Hist0, V2HComp) -> Frag = tls_handshake:encode_handshake(Handshake, Version), - Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag, V2HComp), {Encoded, ConnectionStates} = ssl_record:encode_handshake(Frag, Version, ConnectionStates0), {Encoded, ConnectionStates, Hist}. @@ -480,7 +491,7 @@ decode_alerts(Bin) -> initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, - ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), + ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> @@ -515,23 +526,6 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us flight_buffer = [] }. - -update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) -> - SSLOption = - case OrigSSLOptions#ssl_options.sni_fun of - undefined -> - proplists:get_value(SNIHostname, - OrigSSLOptions#ssl_options.sni_hosts); - SNIFun -> - SNIFun(SNIHostname) - end, - case SSLOption of - undefined -> - undefined; - _ -> - ssl:handle_options(SSLOption, OrigSSLOptions) - end. - next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0, tls_cipher_texts = CT0} = Buffers} = State0) -> case tls_record:get_tls_records(Data, Buf0) of @@ -579,7 +573,7 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> {Record, State} = next_record(State0), next_event(StateName, Record, State); _ -> - {Record, State} = read_application_data(<<>>, State0), + {Record, State} = ssl_connection:read_application_data(<<>>, State0), next_event(StateName, Record, State) end. @@ -591,7 +585,7 @@ next_event(connection = StateName, no_record, State0, Actions) -> {no_record, State} -> ssl_connection:hibernate_after(StateName, State, Actions); {#ssl_tls{} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]}; + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; {#alert{} = Alert, State} -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; @@ -600,169 +594,11 @@ next_event(StateName, Record, State, Actions) -> no_record -> {next_state, StateName, State, Actions}; #ssl_tls{} = Record -> - {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]}; + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; #alert{} = Alert -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. -read_application_data(Data, #state{user_application = {_Mon, Pid}, - socket = Socket, - transport_cb = Transport, - socket_options = SOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - timer = Timer, - user_data_buffer = Buffer0, - tracker = Tracker} = State0) -> - Buffer1 = if - Buffer0 =:= <<>> -> Data; - Data =:= <<>> -> Buffer0; - true -> <<Buffer0/binary, Data/binary>> - end, - case get_data(SOpts, BytesToRead, Buffer1) of - {ok, ClientData, Buffer} -> % Send data - SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom, Tracker), - cancel_timer(Timer), - State = State0#state{user_data_buffer = Buffer, - start_or_recv_from = undefined, - timer = undefined, - bytes_to_read = undefined, - socket_options = SocketOpt - }, - if - SocketOpt#socket_options.active =:= false; Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv - %% Active and empty, get more data - next_record_if_active(State); - true -> %% We have more data - read_application_data(<<>>, State) - end; - {more, Buffer} -> % no reply, we need more data - next_record(State0#state{user_data_buffer = Buffer}); - {passive, Buffer} -> - next_record_if_active(State0#state{user_data_buffer = Buffer}); - {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker), - {stop, normal, State0} - end. - -%% Picks ClientData -get_data(_, _, <<>>) -> - {more, <<>>}; -%% Recv timed out save buffer data until next recv -get_data(#socket_options{active=false}, undefined, Buffer) -> - {passive, Buffer}; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - if - Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Buffer, <<>>}; - byte_size(Buffer) >= BytesToRead -> - %% Passive Mode, recv(Bytes) - <<Data:BytesToRead/binary, Rest/binary>> = Buffer, - {ok, Data, Rest}; - true -> - %% Passive Mode not enough data - {more, Buffer} - end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> - PacketOpts = [{packet_size, Size}], - case decode_packet(Type, Buffer, PacketOpts) of - {more, _} -> - {more, Buffer}; - Decoded -> - Decoded - end. - -decode_packet({http, headers}, Buffer, PacketOpts) -> - decode_packet(httph, Buffer, PacketOpts); -decode_packet({http_bin, headers}, Buffer, PacketOpts) -> - decode_packet(httph_bin, Buffer, PacketOpts); -decode_packet(Type, Buffer, PacketOpts) -> - erlang:decode_packet(Type, Buffer, PacketOpts). - -%% Just like with gen_tcp sockets, an ssl socket that has been configured with -%% {packet, http} (or {packet, http_bin}) will automatically switch to expect -%% HTTP headers after it sees a HTTP Request or HTTP Response line. We -%% represent the current state as follows: -%% #socket_options.packet =:= http: Expect a HTTP Request/Response line -%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers -%% Note that if the user has explicitly configured the socket to expect -%% HTTP headers using the {packet, httph} option, we don't do any automatic -%% switching of states. -deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From, Tracker) -> - send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker)), - SO = case Data of - {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), - ((Type =:= http) or (Type =:= http_bin)) -> - SOpts#socket_options{packet={Type, headers}}; - http_eoh when tuple_size(Type) =:= 2 -> - % End of headers - expect another Request/Response line - {Type1, headers} = Type, - SOpts#socket_options{packet=Type1}; - _ -> - SOpts - end, - case Active of - once -> - SO#socket_options{active=false}; - _ -> - SO - end. - -format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data, _) -> - {ok, do_format_reply(Mode, Packet, Header, Data)}; -format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, - header = Header}, Data, Tracker) -> - {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), - do_format_reply(Mode, Packet, Header, Data)}. - -deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker) -> - send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker)). - -format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _) -> - {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker) -> - {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), - {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. - -do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode - header(N, Data); -do_format_reply(binary, _, _, Data) -> - Data; -do_format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; - Packet == http_bin; Packet == {http_bin, headers}; - Packet == httph; Packet == httph_bin -> - Data; -do_format_reply(list, _,_, Data) -> - binary_to_list(Data). - -header(0, <<>>) -> - <<>>; -header(_, <<>>) -> - []; -header(0, Binary) -> - Binary; -header(N, Binary) -> - <<?BYTE(ByteN), NewBinary/binary>> = Binary, - [ByteN | header(N-1, NewBinary)]. - -send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_statem:reply(From, Data); -%% Can happen when handling own alert or tcp error/close and there is -%% no outstanding gen_fsm sync events -send_or_reply(false, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _From, Data) -> - send_user(Pid, Data). - -send_user(Pid, Msg) -> - Pid ! Msg. - tls_handshake_events([]) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake)); tls_handshake_events(Packets) -> @@ -770,55 +606,7 @@ tls_handshake_events(Packets) -> {next_event, internal, {handshake, Packet}} end, Packets). -write_application_data(Data0, From, - #state{socket = Socket, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0, - socket_options = SockOpts, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> - Data = encode_packet(Data0, SockOpts), - - case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of - true -> - renegotiate(State#state{renegotiation = {true, internal}}, - [{next_event, {call, From}, {application_data, Data0}}]); - false -> - {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates}, - [{reply, From, Result}]) - end. - -encode_packet(Data, #socket_options{packet=Packet}) -> - case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. - -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); - false -> <<Len:Size, Bin/binary>> - end. -time_to_renegotiate(_Data, - #connection_states{current_write = - #connection_state{sequence_number = Num}}, - RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), - %% but we chose to have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. renegotiate(#state{role = client} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation @@ -848,131 +636,10 @@ handle_alerts([], Result) -> handle_alerts(_, {stop,_} = Stop) -> Stop; handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, handle_alert(Alert, StateName, State)); + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> - handle_alerts(Alerts, handle_alert(Alert, StateName, State)). -handle_alert(#alert{level = ?FATAL} = Alert, StateName, - #state{socket = Socket, transport_cb = Transport, - ssl_options = SslOpts, start_or_recv_from = From, host = Host, - port = Port, session = Session, user_application = {_Mon, Pid}, - role = Role, socket_options = Opts, tracker = Tracker}) -> - invalidate_session(Role, Host, Port, Session), - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role), - {stop, normal}; - -handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - StateName, State) -> - handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}}; - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}}; - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{ssl_options = SslOpts, renegotiation = {true, From}} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - gen_statem:reply(From, {error, renegotiation_rejected}), - {Record, State} = next_record(State0), - %% Go back to connection! - next_event(connection, Record, State); - -%% Gracefully log and ignore all other warning alerts -handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{ssl_options = SslOpts} = State0) -> - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - {Record, State} = next_record(State0), - next_event(StateName, Record, State). - -alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role); -alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, From, Alert, Role). - -alert_user(Transport, Tracker, Socket, From, Alert, Role) -> - alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role). - -alert_user(_, _, _, false = Active, Pid, From, Alert, Role) when From =/= undefined -> - %% If there is an outstanding ssl_accept | recv - %% From will be defined and send_or_reply will - %% send the appropriate error message. - ReasonCode = ssl_alert:reason_code(Alert, Role), - send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role) -> - case ssl_alert:reason_code(Alert, Role) of - closed -> - send_or_reply(Active, Pid, From, - {ssl_closed, ssl_socket:socket(self(), - Transport, Socket, ?MODULE, Tracker)}); - ReasonCode -> - send_or_reply(Active, Pid, From, - {ssl_error, ssl_socket:socket(self(), - Transport, Socket, ?MODULE, Tracker), ReasonCode}) - end. - -log_alert(true, Info, Alert) -> - Txt = ssl_alert:alert_txt(Alert), - error_logger:format("SSL: ~p: ~s\n", [Info, Txt]); -log_alert(false, _, _) -> - ok. - -handle_own_alert(Alert, Version, StateName, - #state{transport_cb = Transport, - socket = Socket, - connection_states = ConnectionStates, - ssl_options = SslOpts} = State) -> - try %% Try to tell the other side - {BinMsg, _} = - ssl_alert:encode(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg) - catch _:_ -> %% Can crash if we are in a uninitialized state - ignore - end, - try %% Try to tell the local user - log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) - catch _:_ -> - ok - end, - {stop, {shutdown, own_alert}}. - -handle_normal_shutdown(Alert, _, #state{socket = Socket, - transport_cb = Transport, - start_or_recv_from = StartFrom, - tracker = Tracker, - role = Role, renegotiation = {false, first}}) -> - alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role); + handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). -handle_normal_shutdown(Alert, StateName, #state{socket = Socket, - socket_options = Opts, - transport_cb = Transport, - user_application = {_Mon, Pid}, - tracker = Tracker, - start_or_recv_from = RecvFrom, role = Role}) -> - alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role). - -handle_close_alert(Data, StateName, State0) -> - case next_tls_record(Data, State0) of - {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> - [Alert|_] = decode_alerts(EncAlerts), - handle_normal_shutdown(Alert, StateName, State); - _ -> - ok - end. - -cancel_timer(undefined) -> - ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - ok. - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). %% User closes or recursive call! close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> @@ -1011,31 +678,37 @@ convert_options_partial_chain(Options, up) -> convert_options_partial_chain(Options, down) -> list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))). -handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) -> - case HelloExtensions#hello_extensions.sni of - undefined -> - State0; - #sni{hostname = Hostname} -> - NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), - case NewOptions of - undefined -> - State0; - _ -> - {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbHandle, OwnCert, Key, DHParams} = - ssl_config:init(NewOptions, State0#state.role), - State0#state{ - session = State0#state.session#session{own_certificate = OwnCert}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams, - ssl_options = NewOptions, - sni_hostname = Hostname - } - end +gen_handshake(GenConnection, StateName, Type, Event, + #state{negotiated_version = Version} = State) -> + try GenConnection:StateName(Type, Event, State, ?MODULE) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + +gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + malformed_data), + Version, StateName, State) end; -handle_sni_extension(_, State) -> - State. + +gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> + try handle_info(Event, StateName, State) of + Result -> + Result + catch + _:_ -> + ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 566b7db332..a2486bf752 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -41,7 +41,7 @@ %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), #connection_states{}, +-spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -54,8 +54,7 @@ client_hello(Host, Port, ConnectionStates, } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = tls_record:highest_protocol_version(Versions), - Pending = ssl_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, + #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), Extensions = ssl_handshake:client_hello_extensions(Host, Version, AvailableCipherSuites, @@ -78,14 +77,14 @@ client_hello(Host, Port, ConnectionStates, %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, - #connection_states{} | {inet:port_number(), #session{}, db_handle(), - atom(), #connection_states{}, + ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), + atom(), ssl_record:connection_states(), binary() | undefined, ssl_cipher:key_algo()}, boolean()) -> {tls_record:tls_version(), session_id(), - #connection_states{}, alpn | npn, binary() | undefined}| + ssl_record:connection_states(), alpn | npn, binary() | undefined}| {tls_record:tls_version(), {resumed | new, #session{}}, - #connection_states{}, binary() | undefined, + ssl_record:connection_states(), binary() | undefined, #hello_extensions{}, {ssl_cipher:hash(), ssl_cipher:sign_algo()} | undefined} | #alert{}. %% @@ -109,19 +108,25 @@ hello(#client_hello{client_version = ClientVersion, cipher_suites = CipherSuites} = Hello, #ssl_options{versions = Versions} = SslOpts, Info, Renegotiation) -> - Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), - case ssl_cipher:is_fallback(CipherSuites) of + try + Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), + case ssl_cipher:is_fallback(CipherSuites) of true -> - Highest = tls_record:highest_protocol_version(Versions), - case tls_record:is_higher(Highest, Version) of - true -> - ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end; - false -> - handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) - end. + Highest = tls_record:highest_protocol_version(Versions), + case tls_record:is_higher(Highest, Version) of + true -> + ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end; + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) + end + catch + _:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data) + end. + %%-------------------------------------------------------------------- -spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist(). %% @@ -187,8 +192,13 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId, get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), Body:Length/binary,Rest/binary>>, #ssl_options{v2_hello_compatible = V2Hello} = Opts, Acc) -> Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, - Handshake = decode_handshake(Version, Type, Body, V2Hello), - get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]); + try decode_handshake(Version, Type, Body, V2Hello) of + Handshake -> + get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, handshake_decode_error)) + end; get_tls_handshake_aux(_Version, Data, _, Acc) -> {lists:reverse(Acc), Data}. diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 9348c8bbdd..5331dd1303 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -32,7 +32,7 @@ -include("ssl_cipher.hrl"). %% Handling of incoming data --export([get_tls_records/2]). +-export([get_tls_records/2, init_connection_states/2]). %% Decoding -export([decode_cipher_text/3]). @@ -56,12 +56,28 @@ %%==================================================================== %% Internal application API %%==================================================================== +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) -> + ssl_record:connection_states(). +%% % + % +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role, BeastMitigation) -> + ConnectionEnd = ssl_record:record_protocol_role(Role), + Current = initial_connection_state(ConnectionEnd, BeastMitigation), + Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + #{current_read => Current, + pending_read => Pending, + current_write => Current, + pending_write => Pending}. %%-------------------------------------------------------------------- -spec get_tls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. %% -%% Description: Given old buffer and new data from TCP, packs up a records %% and returns it as a list of tls_compressed binaries also returns leftover +%% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- get_tls_records(Data, <<>>) -> @@ -129,63 +145,61 @@ get_tls_records_aux(Data, Acc) -> end. encode_plain_text(Type, Version, Data, - #connection_states{current_write = - #connection_state{ - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> + #{current_write := + #{sequence_number := Seq, + compression_state := CompS0, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + }= WriteState0} = ConnectionStates) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + WriteState1 = WriteState0#{compression_state => CompS1}, AAD = calc_aad(Type, Version, WriteState1), {CipherFragment, WriteState} = ssl_record:cipher_aead(Version, Comp, WriteState1, AAD), CipherText = encode_tls_cipher_text(Type, Version, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}; + {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}; encode_plain_text(Type, Version, Data, - #connection_states{current_write = - #connection_state{ - sequence_number = Seq, - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }= WriteState0} = ConnectionStates) -> + #{current_write := + #{sequence_number := Seq, + compression_state := CompS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + }= WriteState0} = ConnectionStates) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + WriteState1 = WriteState0#{compression_state => CompS1}, MacHash = calc_mac_hash(Type, Version, Comp, WriteState1), {CipherFragment, WriteState} = ssl_record:cipher(Version, Comp, WriteState1, MacHash), CipherText = encode_tls_cipher_text(Type, Version, CipherFragment), - {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}. + {CipherText, ConnectionStates#{current_write => WriteState#{sequence_number => Seq +1}}}; +encode_plain_text(_,_,_, CS) -> + exit({cs, CS}). %%-------------------------------------------------------------------- --spec decode_cipher_text(#ssl_tls{}, #connection_states{}, boolean()) -> - {#ssl_tls{}, #connection_states{}}| #alert{}. +-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states(), boolean()) -> + {#ssl_tls{}, ssl_record:connection_states()}| #alert{}. %% %% Description: Decode cipher text %%-------------------------------------------------------------------- decode_cipher_text(#ssl_tls{type = Type, version = Version, fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{ - compression_state = CompressionS0, - sequence_number = Seq, - security_parameters= - #security_parameters{ - cipher_type = ?AEAD, - compression_algorithm=CompAlg} - } = ReadState0} = ConnnectionStates0, _) -> + #{current_read := + #{compression_state := CompressionS0, + sequence_number := Seq, + security_parameters := + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm = CompAlg} + } = ReadState0} = ConnnectionStates0, _) -> AAD = calc_aad(Type, Version, ReadState0), case ssl_record:decipher_aead(Version, CipherFragment, ReadState0, AAD) of {PlainFragment, ReadState1} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - sequence_number = Seq + 1, - compression_state = CompressionS1}}, + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState1#{sequence_number => Seq + 1, + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; #alert{} = Alert -> Alert @@ -193,13 +207,12 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, decode_cipher_text(#ssl_tls{type = Type, version = Version, fragment = CipherFragment} = CipherText, - #connection_states{current_read = - #connection_state{ - compression_state = CompressionS0, - sequence_number = Seq, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - } = ReadState0} = ConnnectionStates0, PaddingCheck) -> + #{current_read := + #{compression_state := CompressionS0, + sequence_number := Seq, + security_parameters := + #security_parameters{compression_algorithm = CompAlg} + } = ReadState0} = ConnnectionStates0, PaddingCheck) -> case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of {PlainFragment, Mac, ReadState1} -> MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState1), @@ -207,10 +220,10 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, true -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - sequence_number = Seq + 1, - compression_state = CompressionS1}}, + ConnnectionStates = ConnnectionStates0#{ + current_read => ReadState1#{ + sequence_number => Seq + 1, + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) @@ -375,6 +388,18 @@ is_acceptable_version(_,_) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #{security_parameters => + ssl_record:initial_security_params(ConnectionEnd), + sequence_number => 0, + beast_mitigation => BeastMitigation, + compression_state => undefined, + cipher_state => undefined, + mac_secret => undefined, + secure_renegotiation => undefined, + client_verify_data => undefined, + server_verify_data => undefined + }. lowest_list_protocol_version(Ver, []) -> Ver; @@ -413,15 +438,15 @@ sufficient_tlsv1_2_crypto_support() -> proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). calc_mac_hash(Type, Version, - PlainFragment, #connection_state{sequence_number = SeqNo, - mac_secret = MacSecret, - security_parameters = - SecPars}) -> + PlainFragment, #{sequence_number := SeqNo, + mac_secret := MacSecret, + security_parameters:= + SecPars}) -> Length = erlang:iolist_size(PlainFragment), mac_hash(Version, SecPars#security_parameters.mac_algorithm, MacSecret, SeqNo, Type, Length, PlainFragment). calc_aad(Type, {MajVer, MinVer}, - #connection_state{sequence_number = SeqNo}) -> + #{sequence_number := SeqNo}) -> <<SeqNo:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec index 86e14c033e..0ad94e22bc 100644 --- a/lib/ssl/test/ssl.spec +++ b/lib/ssl/test/ssl.spec @@ -1,4 +1,5 @@ {suites,"../ssl_test",all}. {skip_cases, "../ssl_test", - ssl_bench_SUITE, [setup_sequential, setup_concurrent, payload_simple], + ssl_bench_SUITE, [setup_sequential, setup_concurrent, payload_simple, + use_pem_cache, bypass_pem_cache], "Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index b8a03f578d..258922d128 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -145,7 +145,7 @@ init_per_testcase(TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), end_per_testcase(TestCase, Config), - ssl:start(), + ssl_test_lib:clean_start(), ct:timetrap({seconds, 15}), Config. @@ -159,42 +159,42 @@ end_per_testcase(_TestCase, Config) -> client_ecdh_server_ecdh(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdh_server_rsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdh(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_rsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_verification_opts, Config), - SOpts = proplists:get_value(server_verification_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_ecdsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_rsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), ServerCert = proplists:get_value(certfile, SOpts), ServerKeyFile = proplists:get_value(keyfile, SOpts), {ok, PemBin} = file:read_file(ServerKeyFile), @@ -244,20 +244,20 @@ basic_test(ClientCert, ClientKey, ClientCA, ServerCert, ServerKey, ServerCA, Con check_result(Server, SType, Client, CType), close(Server, Client). -start_client(openssl, Port, CA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), +start_client(openssl, Port, PeerCA, OwnCa, Cert, Key, _Config) -> + CA = new_openssl_ca("openssl_client_ca", PeerCA, OwnCa), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", Cert, "-CAfile", NewCA, + "-cert", Cert, "-CAfile", CA, "-key", Key, "-host","localhost", "-msg", "-debug"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), OpenSslPort; -start_client(erlang, Port, CA, _, Cert, Key, Config) -> +start_client(erlang, Port, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_client_ca", PeerCA, OwnCa), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -267,20 +267,19 @@ start_client(erlang, Port, CA, _, Cert, Key, Config) -> {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]). -start_server(openssl, CA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), - +start_server(openssl, PeerCA, OwnCa, Cert, Key, _Config) -> + CA = new_openssl_ca("openssl_server_ca", PeerCA, OwnCa), Port = ssl_test_lib:inet_port(node()), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-verify", "2", "-cert", Cert, "-CAfile", NewCA, + "-verify", "2", "-cert", Cert, "-CAfile", CA, "-key", Key, "-msg", "-debug"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), {OpenSslPort, Port}; -start_server(erlang, CA, _, Cert, Key, Config) -> +start_server(erlang, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -291,7 +290,8 @@ start_server(erlang, CA, _, Cert, Key, Config) -> [{verify, verify_peer}, {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]), {Server, ssl_test_lib:inet_port(Server)}. -start_server_with_raw_key(erlang, CA, _, Cert, Key, Config) -> +start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -336,17 +336,27 @@ close(Client, Server) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -%% Work around OpenSSL bug, apparently the same bug as we had fixed in -%% 11629690ba61f8e0c93ef9b2b6102fd279825977 new_ca(FileName, CA, OwnCa) -> {ok, P1} = file:read_file(CA), E1 = public_key:pem_decode(P1), {ok, P2} = file:read_file(OwnCa), E2 = public_key:pem_decode(P2), + Pem = public_key:pem_encode(E1 ++E2), + file:write_file(FileName, Pem), + FileName. + +new_openssl_ca(FileName, CA, OwnCa) -> + {ok, P1} = file:read_file(CA), + E1 = public_key:pem_decode(P1), + {ok, P2} = file:read_file(OwnCa), + E2 = public_key:pem_decode(P2), case os:cmd("openssl version") of "OpenSSL 1.0.1p-freebsd" ++ _ -> Pem = public_key:pem_encode(E1 ++E2), file:write_file(FileName, Pem); + "LibreSSL" ++ _ -> + Pem = public_key:pem_encode(E1 ++E2), + file:write_file(FileName, Pem); _ -> Pem = public_key:pem_encode(E2 ++E1), file:write_file(FileName, Pem) diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl index da181faf64..9d57e89b9b 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -71,7 +71,7 @@ init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index efa5faa218..57963fd44b 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -40,6 +40,7 @@ -define(SLEEP, 500). -define(RENEGOTIATION_DISABLE_TIME, 12000). -define(CLEAN_SESSION_DB, 60000). +-define(SEC_RENEGOTIATION_TIMEOUT, 30). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -249,7 +250,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), %% make rsa certs using oppenssl {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), @@ -306,6 +307,7 @@ init_per_testcase(protocol_versions, Config) -> init_per_testcase(reuse_session_expired, Config) -> ssl:stop(), application:load(ssl), + ssl_test_lib:clean_env(), application:set_env(ssl, session_lifetime, ?EXPIRE), application:set_env(ssl, session_delay_cleanup_time, 500), ssl:start(), @@ -315,6 +317,7 @@ init_per_testcase(reuse_session_expired, Config) -> init_per_testcase(empty_protocol_versions, Config) -> ssl:stop(), application:load(ssl), + ssl_test_lib:clean_env(), application:set_env(ssl, protocol_version, []), ssl:start(), ct:timetrap({seconds, 5}), @@ -340,7 +343,7 @@ init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; TestCase == renegotiate_dos_mitigate_passive; TestCase == renegotiate_dos_mitigate_absolute -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 30}), + ct:timetrap({seconds, ?SEC_RENEGOTIATION_TIMEOUT + 5}), Config; init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; @@ -350,6 +353,11 @@ init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; TestCase == ciphers_dsa_signed_certs; TestCase == ciphers_dsa_signed_certs_openssl_names; TestCase == anonymous_cipher_suites; + TestCase == ciphers_ecdsa_signed_certs; + TestCase == ciphers_ecdsa_signed_certs_openssl_names; + TestCase == anonymous_cipher_suites; + TestCase == psk_anon_cipher_suites; + TestCase == psk_anon_with_hint_cipher_suites; TestCase == versions_option, TestCase == tls_tcp_connect_big -> ssl_test_lib:ct_log_supported_protocol_versions(Config), @@ -408,8 +416,13 @@ init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 15}), Config; -init_per_testcase(clear_pem_cache, Config) -> +init_per_testcase(TestCase, Config) when TestCase == clear_pem_cache; + TestCase == der_input; + TestCase == defaults -> ssl_test_lib:ct_log_supported_protocol_versions(Config), + %% White box test need clean start + ssl:stop(), + ssl:start(), ct:timetrap({seconds, 20}), Config; init_per_testcase(raw_ssl_option, Config) -> @@ -430,7 +443,9 @@ init_per_testcase(accept_pool, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), Config end; - +init_per_testcase(controller_dies, Config) -> + ct:timetrap({seconds, 10}), + Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), @@ -441,6 +456,11 @@ end_per_testcase(reuse_session_expired, Config) -> application:unset_env(ssl, session_delay_cleanup_time), end_per_testcase(default_action, Config); +end_per_testcase(Case, Config) when Case == protocol_versions; + Case == empty_protocol_versions-> + application:unset_env(ssl, protocol_versions), + end_per_testcase(default_action, Config); + end_per_testcase(_TestCase, Config) -> Config. @@ -567,8 +587,8 @@ prf(Config) when is_list(Config) -> connection_info() -> [{doc,"Test the API function ssl:connection_information/1"}]. connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1144,8 +1164,8 @@ cipher_suites_mix() -> cipher_suites_mix(Config) when is_list(Config) -> CipherSuites = [{ecdh_rsa,aes_128_cbc,sha256,sha256}, {rsa,aes_128_cbc,sha}], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -4288,7 +4308,7 @@ erlang_ssl_receive(Socket, Data) -> erlang_ssl_receive(Socket, tl(Data)); Other -> ct:fail({unexpected_message, Other}) - after ?SLEEP * 3 * test_server:timetrap_scale_factor() -> + after timer:seconds(?SEC_RENEGOTIATION_TIMEOUT) * test_server:timetrap_scale_factor() -> ct:fail({did_not_get, Data}) end. @@ -4409,14 +4429,14 @@ run_suites(Ciphers, Version, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_opts, Config)}; + {ssl_test_lib:ssl_options(client_verification_opts, Config), + ssl_test_lib:ssl_options(server_verification_opts, Config)}; dsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_dsa_opts, Config)}; anonymous -> %% No certs in opts! - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_anon, Config)}; psk -> {ssl_test_lib:ssl_options(client_psk, Config), @@ -4440,31 +4460,31 @@ run_suites(Ciphers, Version, Config, Type) -> {ssl_test_lib:ssl_options(client_srp_dsa, Config), ssl_test_lib:ssl_options(server_srp_dsa, Config)}; ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; rc4_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]}; + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; rc4_ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; des_dhe_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]}; + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; des_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]} + ssl_test_lib:ssl_options(server_verification_opts, Config)]} end, Result = lists:map(fun(Cipher) -> diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index ed439a425f..21989f8d99 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -25,11 +25,12 @@ suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. -all() -> [{group, setup}, {group, payload}]. +all() -> [{group, setup}, {group, payload}, {group, pem_cache}]. groups() -> [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, - {payload, [{repeat, 3}], [payload_simple]} + {payload, [{repeat, 3}], [payload_simple]}, + {pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]} ]. init_per_group(_GroupName, Config) -> @@ -49,9 +50,33 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_testcase(use_pem_cache, Conf) -> + case bypass_pem_cache_supported() of + false -> {skipped, "PEM cache bypass support required"}; + true -> + application:set_env(ssl, bypass_pem_cache, false), + Conf + end; +init_per_testcase(bypass_pem_cache, Conf) -> + case bypass_pem_cache_supported() of + false -> {skipped, "PEM cache bypass support required"}; + true -> + application:set_env(ssl, bypass_pem_cache, true), + Conf + end; init_per_testcase(_Func, Conf) -> Conf. +end_per_testcase(use_pem_cache, _Config) -> + case bypass_pem_cache_supported() of + false -> ok; + true -> application:set_env(ssl, bypass_pem_cache, false) + end; +end_per_testcase(bypass_pem_cache, _Config) -> + case bypass_pem_cache_supported() of + false -> ok; + true -> application:set_env(ssl, bypass_pem_cache, false) + end; end_per_testcase(_Func, _Conf) -> ok. @@ -94,6 +119,18 @@ payload_simple(Config) -> {suite, "ssl"}, {name, "Payload simple"}]}), ok. +use_pem_cache(_Config) -> + {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Use PEM cache"}]}). + +bypass_pem_cache(_Config) -> + {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Bypass PEM cache"}]}). + ssl() -> test(ssl, ?COUNT, node()). @@ -172,6 +209,18 @@ server_init(ssl, payload, Loop, _, Server) -> ssl:close(TSocket) end, setup_server_connection(Socket, Test); +server_init(ssl, pem_cache, Loop, _, Server) -> + {ok, Socket} = ssl:listen(0, ssl_opts(listen_der)), + {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, Host} = inet:gethostname(), + Server ! {self(), {init, Host, Port}}, + Test = fun(TSocket) -> + ok = ssl:ssl_accept(TSocket), + Size = byte_size(msg()), + server_echo(TSocket, Size, Loop), + ssl:close(TSocket) + end, + setup_server_connection(Socket, Test); server_init(Type, Tc, _, _, Server) -> io:format("No server init code for ~p ~p~n",[Type, Tc]), @@ -185,6 +234,11 @@ client_init(Master, ssl, payload, Host, Port) -> Master ! {self(), init}, Size = byte_size(msg()), {Sock, Size}; +client_init(Master, ssl, pem_cache, Host, Port) -> + {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect_der)), + Master ! {self(), init}, + Size = byte_size(msg()), + {Sock, Size}; client_init(_Me, Type, Tc, Host, Port) -> io:format("No client init code for ~p ~p~n",[Type, Tc]), {Host, Port}. @@ -228,6 +282,13 @@ payload(Loop, ssl, D = {Socket, Size}) when Loop > 0 -> payload(_, _, {Socket, _}) -> ssl:close(Socket). +pem_cache(N, ssl, Data = {Socket, Size}) when N > 0 -> + ok = ssl:send(Socket, msg()), + {ok, _} = ssl:recv(Socket, Size), + pem_cache(N-1, ssl, Data); +pem_cache(_, _, {Socket, _}) -> + ssl:close(Socket). + msg() -> <<"Hello", 0:(512*8), @@ -352,16 +413,43 @@ stop_profile(fprof, File) -> ssl_opts(listen) -> [{backlog, 500} | ssl_opts("server")]; ssl_opts(connect) -> - [{verify, verify_peer} - | ssl_opts("client")]; + [{verify, verify_peer} | ssl_opts("client")]; +ssl_opts(listen_der) -> + [{backlog, 500} | ssl_opts("server_der")]; +ssl_opts(connect_der) -> + [{verify, verify_peer} | ssl_opts("client_der")]; ssl_opts(Role) -> - Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]), + CertData = cert_data(Role), [{active, false}, {depth, 2}, {reuseaddr, true}, {mode,binary}, {nodelay, true}, - {ciphers, [{dhe_rsa,aes_256_cbc,sha}]}, - {cacertfile, filename:join([Dir, Role, "cacerts.pem"])}, + {ciphers, [{dhe_rsa,aes_256_cbc,sha}]} + |CertData]. + +cert_data(Der) when Der =:= "server_der"; Der =:= "client_der" -> + [Role,_] = string:tokens(Der, "_"), + Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]), + {ok, CaCert0} = file:read_file(filename:join([Dir, Role, "cacerts.pem"])), + {ok, Cert0} = file:read_file(filename:join([Dir, Role, "cert.pem"])), + {ok, Key0} = file:read_file(filename:join([Dir, Role, "key.pem"])), + [{_, Cert, _}] = public_key:pem_decode(Cert0), + CaCert1 = public_key:pem_decode(CaCert0), + CaCert = [CCert || {_, CCert, _} <- CaCert1], + [{KeyType, Key, _}] = public_key:pem_decode(Key0), + [{cert, Cert}, + {cacerts, CaCert}, + {key, {KeyType, Key}}]; +cert_data(Role) -> + Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]), + [{cacertfile, filename:join([Dir, Role, "cacerts.pem"])}, {certfile, filename:join([Dir, Role, "cert.pem"])}, {keyfile, filename:join([Dir, Role, "key.pem"])}]. + +bypass_pem_cache_supported() -> + %% This function is currently critical to support cache bypass + %% and did not exist in prior versions. + catch ssl_pkix_db:module_info(), % ensure module is loaded + erlang:function_exported(ssl_pkix_db, extract_trusted_certs, 1). + diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 20165c70f0..4c6f1d7c01 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -65,9 +65,10 @@ tests() -> cert_expired, invalid_signature_client, invalid_signature_server, - extended_key_usage_verify_peer, - extended_key_usage_verify_none, - critical_extension_verify_peer, + extended_key_usage_verify_client, + extended_key_usage_verify_server, + critical_extension_verify_client, + critical_extension_verify_server, critical_extension_verify_none]. error_handling_tests()-> @@ -84,7 +85,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), %% make rsa certs using oppenssl {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), @@ -122,6 +123,8 @@ init_per_testcase(TestCase, Config) when TestCase == cert_expired; ssl:clear_pem_cache(), init_per_testcase(common, Config); init_per_testcase(_TestCase, Config) -> + ssl:stop(), + ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), Config. @@ -136,7 +139,7 @@ end_per_testcase(_TestCase, Config) -> verify_peer() -> [{doc,"Test option verify_peer"}]. verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -190,7 +193,7 @@ server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. server_verify_client_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -230,7 +233,7 @@ server_require_peer_cert_ok() -> server_require_peer_cert_ok(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -259,7 +262,7 @@ server_require_peer_cert_fail() -> server_require_peer_cert_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - BadClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + BadClientOpts = ssl_test_lib:ssl_options(client_opts, []), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -290,7 +293,7 @@ server_require_peer_cert_partial_chain() -> server_require_peer_cert_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), @@ -325,13 +328,13 @@ server_require_peer_cert_allow_partial_chain() -> server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ClientCAs), PartialChain = fun(CertChain) -> case lists:member(IntermidiateCA, CertChain) of @@ -367,7 +370,7 @@ server_require_peer_cert_do_not_allow_partial_chain() -> server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), @@ -408,7 +411,7 @@ server_require_peer_cert_partial_chain_fun_fail() -> server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = proplists:get_value(client_verification_opts, Config), + ClientOpts = proplists:get_value(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), @@ -448,7 +451,7 @@ verify_fun_always_run_client() -> verify_fun_always_run_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -492,7 +495,7 @@ verify_fun_always_run_client(Config) when is_list(Config) -> verify_fun_always_run_server() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -524,9 +527,7 @@ verify_fun_always_run_server(Config) when is_list(Config) -> {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, - [{verify, verify_peer} - | ClientOpts]}]), + {options, ClientOpts}]), %% Client error may be {tls_alert, "handshake failure" } or closed depending on timing %% this is not a bug it is a circumstance of how tcp works! @@ -544,7 +545,7 @@ cert_expired() -> cert_expired(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), @@ -607,11 +608,11 @@ two_digits_str(N) -> lists:flatten(io_lib:format("~p", [N])). %%-------------------------------------------------------------------- -extended_key_usage_verify_peer() -> - [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode"}]. +extended_key_usage_verify_server() -> + [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode for server"}]. -extended_key_usage_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), +extended_key_usage_verify_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), @@ -660,7 +661,7 @@ extended_key_usage_verify_peer(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | + {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -669,12 +670,12 @@ extended_key_usage_verify_peer(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -extended_key_usage_verify_none() -> - [{doc,"Test cert that has a critical extended_key_usage extension in verify_none mode"}]. +extended_key_usage_verify_client() -> + [{doc,"Test cert that has a critical extended_key_usage extension in client verify_peer mode"}]. -extended_key_usage_verify_none(Config) when is_list(Config) -> +extended_key_usage_verify_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -730,11 +731,11 @@ extended_key_usage_verify_none(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -critical_extension_verify_peer() -> +critical_extension_verify_server() -> [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. -critical_extension_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), +critical_extension_verify_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), @@ -766,7 +767,7 @@ critical_extension_verify_peer(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), %% This certificate has a critical extension that we don't %% understand. Therefore, verification should fail. @@ -775,14 +776,60 @@ critical_extension_verify_peer(Config) when is_list(Config) -> ssl_test_lib:close(Server), ok. +%%-------------------------------------------------------------------- + +critical_extension_verify_client() -> + [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. + +critical_extension_verify_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + + KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), + NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), + add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), + NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], + + ClientCertFile = proplists:get_value(certfile, ClientOpts), + NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), + add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), + NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + + %% This certificate has a critical extension that we don't + %% understand. Therefore, verification should fail. + tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, + Client, {error, {tls_alert, "unsupported certificate"}}), + ssl_test_lib:close(Server), + ok. %%-------------------------------------------------------------------- critical_extension_verify_none() -> [{doc,"Test cert that has a critical unknown extension in verify_none mode"}]. critical_extension_verify_none(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -1070,7 +1117,7 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> server_verify_no_cacerts() -> [{doc,"Test server must have cacerts if it wants to verify client"}]. server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_opts, Config)), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1084,7 +1131,7 @@ server_verify_no_cacerts(Config) when is_list(Config) -> unknown_server_ca_fail() -> [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -1128,7 +1175,7 @@ unknown_server_ca_fail(Config) when is_list(Config) -> unknown_server_ca_accept_verify_none() -> [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1153,7 +1200,7 @@ unknown_server_ca_accept_verify_peer() -> [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" " with a verify_fun that accepts the unknown ca error"}]. unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts =ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1192,7 +1239,7 @@ unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> unknown_server_ca_accept_backwardscompatibility() -> [{doc,"Test that old style verify_funs will work"}]. unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index 00636e5660..bc2822f0c4 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -72,7 +72,7 @@ init_per_suite(Config) -> false -> {skip, io_lib:format("Bad openssl version: ~p",[OpenSSL_version])}; _ -> - catch crypto:stop(), + end_per_suite(Config), try crypto:start() of ok -> {ok, Hostname0} = inet:gethostname(), @@ -136,7 +136,7 @@ init_per_testcase(Case, Config0) -> true -> end_per_testcase(Case, Config0), inets:start(), - ssl:start(), + ssl_test_lib:clean_start(), ServerRoot = make_dir_path([proplists:get_value(priv_dir, Config0), idp_crl, tmp]), %% start a HTTP server to serve the CRLs {ok, Httpd} = inets:start(httpd, [{ipfamily, proplists:get_value(ipfamily, Config0)}, @@ -155,7 +155,7 @@ init_per_testcase(Case, Config0) -> [{cert_dir, CertDir} | Config]; false -> end_per_testcase(Case, Config0), - ssl:start(), + ssl_test_lib:clean_start(), Config0 end. diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 5ebf9bb2de..8740e8c8f0 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -109,11 +109,11 @@ common_end(_, _Config) -> basic() -> [{doc,"Test that two nodes can connect via ssl distribution"}]. basic(Config) when is_list(Config) -> - NH1 = start_ssl_node(Config), + gen_dist_test(basic_test, Config). + +basic_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(Config), Node2 = NH2#node_handle.nodename, - pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), @@ -161,18 +161,16 @@ basic(Config) when is_list(Config) -> ok end end) - end, - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + end. %%-------------------------------------------------------------------- payload() -> [{doc,"Test that send a lot of data between the ssl distributed noes"}]. payload(Config) when is_list(Config) -> - NH1 = start_ssl_node(Config), + gen_dist_test(payload_test, Config). + +payload_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(Config), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -204,10 +202,8 @@ payload(Config) when is_list(Config) -> ok end end) - end, - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + end. + %%-------------------------------------------------------------------- plain_options() -> [{doc,"Test specifying additional options"}]. @@ -218,20 +214,17 @@ plain_options(Config) when is_list(Config) -> "client_verify verify_none server_verify verify_none " "server_depth 1 client_depth 1 " "server_hibernate_after 500 client_hibernate_after 500", + gen_dist_test(plain_options_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +plain_options_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- plain_verify_options() -> [{doc,"Test specifying additional options"}]. @@ -240,20 +233,18 @@ plain_verify_options(Config) when is_list(Config) -> "client_secure_renegotiate true " "server_reuse_sessions true client_reuse_sessions true " "server_hibernate_after 500 client_hibernate_after 500", + gen_dist_test(plain_verify_options_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +plain_verify_options_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, - + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), - + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). + - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- nodelay_option() -> [{doc,"Test specifying dist_nodelay option"}]. @@ -265,6 +256,7 @@ nodelay_option(Config) -> after application:unset_env(kernel, dist_nodelay) end. +%%-------------------------------------------------------------------- listen_port_options() -> [{doc, "Test specifying listening ports"}]. @@ -285,32 +277,39 @@ listen_port_options(Config) when is_list(Config) -> #node_handle{} -> %% If the node was able to start, it didn't take the port %% option into account. + stop_ssl_node(NH1), exit(unexpected_success) catch exit:{accept_failed, timeout} -> %% The node failed to start, as expected. ok end, - + %% Try again, now specifying a high max port. PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ - " inet_dist_listen_max 65535", + " inet_dist_listen_max 65535", NH2 = start_ssl_node([{additional_dist_opts, PortOpt2} | Config]), - Node2 = NH2#node_handle.nodename, - Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), - {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), - {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), - - %% The new port should be higher: - if Port2 > Port1 -> - ok; - true -> - error({port, Port2, not_higher_than, Port1}) + + try + Node2 = NH2#node_handle.nodename, + Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), + {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), + {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), + + %% The new port should be higher: + if Port2 > Port1 -> + ok; + true -> + error({port, Port2, not_higher_than, Port1}) + end + catch + _:Reason -> + stop_ssl_node(NH2), + ct:fail(Reason) end, - - stop_ssl_node(NH1), stop_ssl_node(NH2), success(Config). + %%-------------------------------------------------------------------- listen_options() -> [{doc, "Test inet_dist_listen_options"}]. @@ -329,28 +328,25 @@ do_listen_options(Prio, Config) -> end, Options = "-kernel inet_dist_listen_options " ++ PriorityString, - - NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), - Node2 = NH2#node_handle.nodename, - + gen_dist_test(listen_options_test, [{prio, Prio}, {additional_dist_opts, Options} | Config]). + +listen_options_test(NH1, NH2, Config) -> + Prio = proplists:get_value(prio, Config), + Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), PrioritiesNode1 = apply_on_ssl_node(NH1, fun get_socket_priorities/0), PrioritiesNode2 = apply_on_ssl_node(NH2, fun get_socket_priorities/0), - + Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], - ?t:format("Elevated1: ~p~n", [Elevated1]), + ct:pal("Elevated1: ~p~n", [Elevated1]), Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], - ?t:format("Elevated2: ~p~n", [Elevated2]), + ct:pal("Elevated2: ~p~n", [Elevated2]), [_|_] = Elevated1, - [_|_] = Elevated2, + [_|_] = Elevated2. - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- connect_options() -> [{doc, "Test inet_dist_connect_options"}]. @@ -369,9 +365,11 @@ do_connect_options(Prio, Config) -> end, Options = "-kernel inet_dist_connect_options " ++ PriorityString, + gen_dist_test(connect_options_test, + [{prio, Prio}, {additional_dist_opts, Options} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), +connect_options_test(NH1, NH2, Config) -> + Prio = proplists:get_value(prio, Config), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -382,17 +380,14 @@ do_connect_options(Prio, Config) -> apply_on_ssl_node(NH2, fun get_socket_priorities/0), Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], - ?t:format("Elevated1: ~p~n", [Elevated1]), + ct:pal("Elevated1: ~p~n", [Elevated1]), Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], - ?t:format("Elevated2: ~p~n", [Elevated2]), + ct:pal("Elevated2: ~p~n", [Elevated2]), %% Node 1 will have a socket with elevated priority. [_|_] = Elevated1, %% Node 2 will not, since it only applies to outbound connections. - [] = Elevated2, + [] = Elevated2. - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- use_interface() -> [{doc, "Test inet_dist_use_interface"}]. @@ -403,22 +398,28 @@ use_interface(Config) when is_list(Config) -> %% Start a node, and get the port number it's listening on. NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - Node1 = NH1#node_handle.nodename, - Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), - {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), - {Name, Port} = lists:keyfind(Name, 1, NodesPorts), - - %% Now find the socket listening on that port, and check its sockname. - Sockets = apply_on_ssl_node( - NH1, - fun() -> - [inet:sockname(P) || - P <- inet_ports(), - {ok, Port} =:= (catch inet:port(P))] - end), - %% And check that it's actually listening on localhost. - [{ok,{{127,0,0,1},Port}}] = Sockets, - + + try + Node1 = NH1#node_handle.nodename, + Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name, Port} = lists:keyfind(Name, 1, NodesPorts), + + %% Now find the socket listening on that port, and check its sockname. + Sockets = apply_on_ssl_node( + NH1, + fun() -> + [inet:sockname(P) || + P <- inet_ports(), + {ok, Port} =:= (catch inet:port(P))] + end), + %% And check that it's actually listening on localhost. + [{ok,{{127,0,0,1},Port}}] = Sockets + catch + _:Reason -> + stop_ssl_node(NH1), + ct:fail(Reason) + end, stop_ssl_node(NH1), success(Config). %%-------------------------------------------------------------------- @@ -430,11 +431,11 @@ verify_fun_fail(Config) when is_list(Config) -> "\"{ssl_dist_SUITE,verify_fail_always,{}}\" " "client_verify verify_peer client_verify_fun " "\"{ssl_dist_SUITE,verify_fail_always,{}}\" ", + gen_dist_test(verify_fun_fail_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +verify_fun_fail_test(NH1, NH2, _) -> Node2 = NH2#node_handle.nodename, - + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), @@ -446,25 +447,9 @@ verify_fun_fail(Config) when is_list(Config) -> %% On the server node, it wouldn't run, because the server didn't %% request a certificate from the client. undefined = - apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end), + apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). -verify_fail_always(_Certificate, _Event, _State) -> - %% Create an ETS table, to record the fact that the verify function ran. - %% Spawn a new process, to avoid the ETS table disappearing. - Parent = self(), - spawn( - fun() -> - ets:new(verify_fun_ran, [public, named_table]), - ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), - Parent ! go_ahead, - timer:sleep(infinity) - end), - receive go_ahead -> ok end, - {fail, bad_certificate}. %%-------------------------------------------------------------------- verify_fun_pass() -> @@ -476,10 +461,10 @@ verify_fun_pass(Config) when is_list(Config) -> "server_fail_if_no_peer_cert true " "client_verify verify_peer client_verify_fun " "\"{ssl_dist_SUITE,verify_pass_always,{}}\" ", + gen_dist_test(verify_fun_pass_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +verify_fun_pass_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -494,25 +479,8 @@ verify_fun_pass(Config) when is_list(Config) -> %% requested and verified the client's certificate because we %% passed fail_if_no_peer_cert. [{verify_pass_always_ran, true}] = - apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end), + apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). - -verify_pass_always(_Certificate, _Event, State) -> - %% Create an ETS table, to record the fact that the verify function ran. - %% Spawn a new process, to avoid the ETS table disappearing. - Parent = self(), - spawn( - fun() -> - ets:new(verify_fun_ran, [public, named_table]), - ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), - Parent ! go_ahead, - timer:sleep(infinity) - end), - receive go_ahead -> ok end, - {valid, State}. %%-------------------------------------------------------------------- crl_check_pass() -> [{doc,"Test crl_check with non-revoked certificate"}]. @@ -520,10 +488,10 @@ crl_check_pass(Config) when is_list(Config) -> DistOpts = "-ssl_dist_opt client_crl_check true", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_pass_test, NewConfig). - NH1 = start_ssl_node(NewConfig), +crl_check_pass_test(NH1, NH2, Config) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, PrivDir = ?config(priv_dir, Config), @@ -533,11 +501,7 @@ crl_check_pass(Config) when is_list(Config) -> pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_check_fail() -> @@ -549,10 +513,9 @@ crl_check_fail(Config) when is_list(Config) -> %% The server uses a revoked certificate. {server_cert_dir, "revoked"}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_fail_test, NewConfig). - NH1 = start_ssl_node(NewConfig), - %%Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), +crl_check_fail_test(NH1, NH2, Config) -> Node2 = NH2#node_handle.nodename, PrivDir = ?config(priv_dir, Config), @@ -562,11 +525,7 @@ crl_check_fail(Config) when is_list(Config) -> pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_check_best_effort() -> @@ -576,22 +535,18 @@ crl_check_best_effort(Config) when is_list(Config) -> "server_verify verify_peer server_crl_check best_effort", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_best_effort_test, NewConfig). +crl_check_best_effort_test(NH1, NH2, _Config) -> %% We don't have the correct CRL at hand, but since crl_check is %% best_effort, we accept it anyway. - NH1 = start_ssl_node(NewConfig), Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_cache_check_pass() -> @@ -605,20 +560,16 @@ crl_cache_check_pass(Config) when is_list(Config) -> "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_cache_check_pass_test, NewConfig). - NH1 = start_ssl_node(NewConfig), +crl_cache_check_pass_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_cache_check_fail() -> @@ -636,44 +587,31 @@ crl_cache_check_fail(Config) when is_list(Config) -> {server_cert_dir, "revoked"}, {additional_dist_opts, DistOpts}] ++ Config, - NH1 = start_ssl_node(NewConfig), - NH2 = start_ssl_node(NewConfig), - Node2 = NH2#node_handle.nodename, + gen_dist_test(crl_cache_check_fail_test, NewConfig). +crl_cache_check_fail_test(NH1, NH2, _) -> + Node2 = NH2#node_handle.nodename, pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). - -%% ssl_crl_cache_api callbacks -lookup(_DistributionPoint, _DbHandle) -> - not_available. - -select({rdnSequence, NameParts}, {NodeDir, _}) -> - %% Extract the CN from the issuer name... - [CN] = [CN || - [#'AttributeTypeAndValue'{ - type = ?'id-at-commonName', - value = <<_, _, CN/binary>>}] <- NameParts], - %% ...and use that as the directory name to find the CRL. - error_logger:info_report([{found_cn, CN}]), - CRLFile = filename:join([NodeDir, CN, "crl.pem"]), - {ok, PemBin} = file:read_file(CRLFile), - PemEntries = public_key:pem_decode(PemBin), - CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} - <- PemEntries], - CRLs. - -fresh_crl(_DistributionPoint, CRL) -> - CRL. - + [] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- +gen_dist_test(Test, Config) -> + NH1 = start_ssl_node(Config), + NH2 = start_ssl_node(Config), + try + ?MODULE:Test(NH1, NH2, Config) + catch + _:Reason -> + stop_ssl_node(NH1), + stop_ssl_node(NH2), + ct:fail(Reason) + end, + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). %% ssl_node side api %% @@ -742,13 +680,15 @@ stop_ssl_node(#node_handle{connection_handler = Handler, receive {'DOWN', Mon, process, Handler, Reason} -> case Reason of - normal -> ok; - _ -> exit(Reason) + normal -> + ok; + _ -> + ct:pal("Down ~p ~n", [Reason]) end end; Error -> erlang:demonitor(Mon, [flush]), - exit(Error) + ct:pal("Warning ~p ~n", [Error]) end. start_ssl_node(Config) -> @@ -1226,3 +1166,53 @@ vsn(App) -> after application:stop(ssl) end. + +verify_fail_always(_Certificate, _Event, _State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {fail, bad_certificate}. + +verify_pass_always(_Certificate, _Event, State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {valid, State}. + +%% ssl_crl_cache_api callbacks +lookup(_DistributionPoint, _DbHandle) -> + not_available. + +select({rdnSequence, NameParts}, {NodeDir, _}) -> + %% Extract the CN from the issuer name... + [CN] = [CN || + [#'AttributeTypeAndValue'{ + type = ?'id-at-commonName', + value = <<_, _, CN/binary>>}] <- NameParts], + %% ...and use that as the directory name to find the CRL. + error_logger:info_report([{found_cn, CN}]), + CRLFile = filename:join([NodeDir, CN, "crl.pem"]), + {ok, PemBin} = file:read_file(CRLFile), + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + CRLs. + +fresh_crl(_DistributionPoint, CRL) -> + CRL. diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index a671e3e307..51f0651568 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -60,7 +60,7 @@ init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) -> ok -> case is_supported(sha512) of true -> - ssl:start(), + ssl_test_lib:clean_start(), %% make rsa certs using oppenssl {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index c55fa73cfb..a02881f1ae 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -68,7 +68,7 @@ init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 00eb9fee4f..69aeea10c5 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -41,6 +41,15 @@ all() -> create_server_hello_with_advertised_protocols_test, create_server_hello_with_no_advertised_protocols_test]. +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), @@ -126,15 +135,12 @@ create_server_handshake(Npn) -> }, Vsn). create_connection_states() -> - #connection_states{ - pending_read = #connection_state{ - security_parameters = #security_parameters{ + #{pending_read => #{security_parameters => #security_parameters{ server_random = <<1:256>>, compression_algorithm = 1, cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA } - }, - current_read = #connection_state { - secure_renegotiation = false - } - }. + }, + current_read => #{secure_renegotiation => false + } + }. diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index e49d432c21..81a49776e4 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -41,7 +41,7 @@ -define(MANY, 1000). -define(SOME, 50). --define(BASE_TIMEOUT_SECONDS, 15). +-define(BASE_TIMEOUT_SECONDS, 30). -define(SOME_SCALE, 20). -define(MANY_SCALE, 20). @@ -140,7 +140,7 @@ init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) @@ -162,6 +162,7 @@ init_per_group(GroupName, Config) -> {skip, "Missing crypto support"} end; _ -> + ssl:stop(), ssl:start(), Config end. @@ -276,6 +277,7 @@ packet_raw_active_once_many_small() -> [{doc,"Test packet option {packet, raw} in active once mode."}]. packet_raw_active_once_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, raw}", packet(Config, Data, send_raw, active_once_raw, ?MANY, raw, once). @@ -392,6 +394,7 @@ packet_0_active_some_big() -> [{doc,"Test packet option {packet, 0} in active mode."}]. packet_0_active_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, active_raw, ?SOME, 0, true). @@ -427,6 +430,7 @@ packet_2_active_some_big() -> [{doc,"Test packet option {packet, 2} in active mode"}]. packet_2_active_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, active_packet, ?SOME, 2, true). @@ -1900,6 +1904,31 @@ header_decode_two_bytes_one_sent_passive(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- + +packet(Config, Data, Send, Recv, Quantity, Packet, Active) when Packet == 0; + Packet == raw -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, Send ,[Data, Quantity]}}, + {options, [{nodelay, true},{packet, Packet} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, Recv, [Data, Quantity]}}, + {options, [{active, Active}, {nodelay, true}, + {packet, Packet} | + ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client); + packet(Config, Data, Send, Recv, Quantity, Packet, Active) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index cb0571d0a7..cb1957327a 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -70,7 +70,7 @@ init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> @@ -104,8 +104,13 @@ init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; TestCase == client_echos_passive_huge; TestCase == client_echos_active_once_huge; TestCase == client_echos_active_huge -> - ct:timetrap({seconds, 90}), - Config; + case erlang:system_info(system_architecture) of + "sparc-sun-solaris2.10" -> + {skip,"Will take to long time on an old Sparc"}; + _ -> + ct:timetrap({seconds, 90}), + Config + end; init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; TestCase == server_echos_active_once_big; diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 13b0ce8ed9..02c98fc40f 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -43,7 +43,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), %% make rsa certs using oppenssl {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), @@ -63,14 +63,15 @@ end_per_group(_GroupName, Config) -> Config. init_per_testcase(pem_cleanup = Case, Config) -> - end_per_testcase(Case, Config) , application:load(ssl), + end_per_testcase(Case, Config) , application:set_env(ssl, ssl_pem_cache_clean, ?CLEANUP_INTERVAL), ssl:start(), ct:timetrap({minutes, 1}), Config. end_per_testcase(_TestCase, Config) -> + ssl_test_lib:clean_env(), ssl:stop(), Config. diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index b352844ba0..28637fc32d 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -58,7 +58,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), %% make rsa certs using {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 34ef2e6af9..4e916a7f03 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -41,7 +41,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), ssl_test_lib:cert_options(Config0) diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 27c670cdc2..81f16030f7 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -385,7 +385,9 @@ cert_options(Config) -> SNIServerAKeyFile = filename:join([proplists:get_value(priv_dir, Config), "a.server", "key.pem"]), SNIServerBCertFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "cert.pem"]), SNIServerBKeyFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "key.pem"]), - [{client_opts, []}, + [{client_opts, [{cacertfile, ClientCaCertFile}, + {certfile, ClientCertFile}, + {keyfile, ClientKeyFile}]}, {client_verification_opts, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, @@ -394,7 +396,7 @@ cert_options(Config) -> {certfile, ClientCertFileDigitalSignatureOnly}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, - {server_opts, [{ssl_imp, new},{reuseaddr, true}, + {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_anon, [{ssl_imp, new},{reuseaddr, true}, {ciphers, anonymous_suites()}]}, {client_psk, [{ssl_imp, new},{reuseaddr, true}, @@ -494,7 +496,7 @@ make_ecdsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, + {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -519,7 +521,7 @@ make_ecdh_rsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, + {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -805,16 +807,24 @@ send_selected_port(_,_,_) -> rsa_suites(CounterPart) -> ECC = is_sane_ecc(CounterPart), FIPS = is_fips(CounterPart), + CryptoSupport = crypto:supports(), + Ciphers = proplists:get_value(ciphers, CryptoSupport), lists:filter(fun({rsa, des_cbc, sha}) when FIPS == true -> false; ({dhe_rsa, des_cbc, sha}) when FIPS == true -> false; - ({rsa, _, _}) -> - true; - ({dhe_rsa, _, _}) -> - true; - ({ecdhe_rsa, _, _}) when ECC == true -> - true; + ({rsa, Cipher, _}) -> + lists:member(Cipher, Ciphers); + ({dhe_rsa, Cipher, _}) -> + lists:member(Cipher, Ciphers); + ({ecdhe_rsa, Cipher, _}) when ECC == true -> + lists:member(Cipher, Ciphers); + ({rsa, Cipher, _, _}) -> + lists:member(Cipher, Ciphers); + ({dhe_rsa, Cipher, _,_}) -> + lists:member(Cipher, Ciphers); + ({ecdhe_rsa, Cipher, _,_}) when ECC == true -> + lists:member(Cipher, Ciphers); (_) -> false end, @@ -1345,3 +1355,19 @@ ct_log_supported_protocol_versions(Config) -> _ -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]) end. + +clean_env() -> + application:unset_env(ssl, protocol_version), + application:unset_env(ssl, session_lifetime), + application:unset_env(ssl, session_cb), + application:unset_env(ssl, session_cb_init_args), + application:unset_env(ssl, session_cache_client_max), + application:unset_env(ssl, session_cache_server_max), + application:unset_env(ssl, ssl_pem_cache_clean), + application:unset_env(ssl, alert_timeout). + +clean_start() -> + ssl:stop(), + application:load(ssl), + clean_env(), + ssl:start(). diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index b3109b5de9..9ecfe5b0ea 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -55,7 +55,9 @@ groups() -> basic_tests() -> [basic_erlang_client_openssl_server, basic_erlang_server_openssl_client, - expired_session]. + expired_session, + ssl2_erlang_server_openssl_client_comp + ]. all_versions_tests() -> [ @@ -74,7 +76,8 @@ all_versions_tests() -> ciphers_dsa_signed_certs, erlang_client_bad_openssl_server, expired_session, - ssl2_erlang_server_openssl_client]. + ssl2_erlang_server_openssl_client + ]. alpn_tests() -> [erlang_client_alpn_openssl_server_alpn, @@ -116,7 +119,7 @@ init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> - ssl:start(), + ssl_test_lib:clean_start(), {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), proplists:get_value(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), @@ -180,7 +183,8 @@ special_init(TestCase, Config) {ok, Version} = application:get_env(ssl, protocol_version), check_sane_openssl_renegotaite(Config, Version); -special_init(ssl2_erlang_server_openssl_client, Config) -> +special_init(Case, Config) when Case == ssl2_erlang_server_openssl_client; + Case == ssl2_erlang_server_openssl_client_comp -> case ssl_test_lib:supports_ssl_tls_version(sslv2) of true -> Config; @@ -954,8 +958,52 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> Data = "From openssl to erlang", Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Exe = "openssl", + Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + "-ssl2", "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + true = port_command(OpenSslPort, Data), + + ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), + receive + {'EXIT', OpenSslPort, _} = Exit -> + ct:log("Received: ~p ~n", [Exit]), + ok + end, + receive + {'EXIT', _, _} = UnkownExit -> + Msg = lists:flatten(io_lib:format("Received: ~p ~n", [UnkownExit])), + ct:log(Msg), + ct:comment(Msg), + ok + after 0 -> + ok + end, + ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}), + process_flag(trap_exit, false). +%%-------------------------------------------------------------------- +ssl2_erlang_server_openssl_client_comp() -> + [{doc,"Test that ssl v2 clients are rejected"}]. + +ssl2_erlang_server_openssl_client_comp(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + V2Compat = proplists:get_value(v2_hello_compatible, Config), + + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, - {options, ServerOpts}]), + {options, [{v2_hello_compatible, V2Compat} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Exe = "openssl", @@ -1264,7 +1312,7 @@ client_check_result(Port, DataExpected, DataReceived) -> _ -> client_check_result(Port, DataExpected, NewData) end - after 3000 -> + after 20000 -> ct:fail({"Time out on openSSL Client", {expected, DataExpected}, {got, DataReceived}}) end. diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl index 113b3b4158..f6af1e6182 100644 --- a/lib/ssl/test/ssl_upgrade_SUITE.erl +++ b/lib/ssl/test/ssl_upgrade_SUITE.erl @@ -29,7 +29,8 @@ server, client, soft, - result_proxy + result_proxy, + skip }). all() -> @@ -73,8 +74,15 @@ major_upgrade(Config) when is_list(Config) -> minor_upgrade(Config) when is_list(Config) -> ct_release_test:upgrade(ssl, minor,{?MODULE, #state{config = Config}}, Config). -upgrade_init(CTData, #state{config = Config} = State) -> - {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssl), +upgrade_init(CtData, State) -> + {ok,{FromVsn,ToVsn}} = ct_release_test:get_app_vsns(CtData, ssl), + upgrade_init(FromVsn, ToVsn, CtData, State). + +upgrade_init(_, "8.0.2", _, State) -> + %% Requires stdlib upgrade so it will be a node upgrade! + State#state{skip = true}; +upgrade_init(_, _, CtData, #state{config = Config} = State) -> + {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CtData, ssl), ct:pal("Up: ~p", [Up]), Soft = is_soft(Up), %% It is symmetrical, if upgrade is soft so is downgrade Pid = spawn(?MODULE, result_proxy_init, [[]]), @@ -88,6 +96,8 @@ upgrade_init(CTData, #state{config = Config} = State) -> State#state{soft = Soft, result_proxy = Pid} end. +upgrade_upgraded(_, #state{skip = true} = State) -> + State; upgrade_upgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> ct:pal("Restart upgrade ~n", []), {Server, Client} = restart_start_connection(Config, Pid), @@ -96,7 +106,6 @@ upgrade_upgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = ssl_test_lib:close(Client), ok = Result, State; - upgrade_upgraded(_, #state{server = Server0, client = Client0, config = Config, soft = true, result_proxy = Pid} = State) -> @@ -110,6 +119,8 @@ upgrade_upgraded(_, #state{server = Server0, client = Client0, {Server, Client} = soft_start_connection(Config, Pid), State#state{server = Server, client = Client}. +upgrade_downgraded(_, #state{skip = true} = State) -> + State; upgrade_downgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> ct:pal("Restart downgrade: ~n", []), {Server, Client} = restart_start_connection(Config, Pid), @@ -119,7 +130,6 @@ upgrade_downgraded(_, #state{soft = false, config = Config, result_proxy = Pid} Pid ! stop, ok = Result, State; - upgrade_downgraded(_, #state{server = Server, client = Client, soft = true, result_proxy = Pid} = State) -> ct:pal("Soft downgrade: ~n", []), Server ! changed_version, diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 6afac59109..914eb43505 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 8.0.1 +SSL_VSN = 8.0.2 diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml index 3b134d00b7..2e4261d72e 100644 --- a/lib/stdlib/doc/src/dets.xml +++ b/lib/stdlib/doc/src/dets.xml @@ -391,7 +391,7 @@ <item> <p><c>{hash, Hash}</c> - Describes which BIF is used to calculate the hash values of the objects stored in the - <c>dets</c> table. Possible values of <c>Hash</c>:</p> + Dets table. Possible values of <c>Hash</c>:</p> <list> <item> <p><c>hash</c> - Implies that the <c>erlang:hash/2</c> BIF diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml index b8e262208d..5f5d2b7f36 100644 --- a/lib/stdlib/doc/src/ets.xml +++ b/lib/stdlib/doc/src/ets.xml @@ -369,7 +369,7 @@ variable that in turn is passed to the function.</p> <p>The parse transform is provided in the <c>ms_transform</c> module and the source <em>must</em> include - file <c>ms_transform.hrl</c> in <c>STDLIB</c> for this + file <c>ms_transform.hrl</c> in STDLIB for this pseudo function to work. Failing to include the hrl file in the source results in a runtime error, not a compile time error. The include file is easiest included by adding line @@ -1458,7 +1458,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> specification returned <c>true</c>.</fsummary> <desc> <p>Matches the objects in table <c><anno>Tab</anno></c> using a - <seealso marker="#match_spec">match specificationc</seealso>. If the + <seealso marker="#match_spec">match specification</seealso>. If the match specification returns <c>true</c> for an object, that object considered a match and is counted. For any other result from the match specification the object is not considered a match and is @@ -1644,7 +1644,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> </taglist> <p>Whenever option <c>extended_info</c> is used, it results in a file not readable by versions of ETS before - that in <c>STDLIB</c> 1.15.1</p> + that in STDLIB 1.15.1</p> <p>If option <c>sync</c> is set to <c>true</c>, it ensures that the content of the file is written to the disk before <c>tab2file</c> returns. Defaults to <c>{sync, false}</c>.</p> @@ -1725,7 +1725,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> <p>A tuple <c>{<anno>Major</anno>,<anno>Minor</anno>}</c> containing the major and minor version of the file format for ETS table dumps. This - version field was added beginning with <c>STDLIB</c> 1.5.1. + version field was added beginning with STDLIB 1.5.1. Files dumped with older versions return <c>{0,0}</c> in this field.</p> </item> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c57a31fa21..3322571b2c 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -97,6 +97,9 @@ gen_statem module Callback module gen_statem:start gen_statem:start_link -----> Module:init/1 +Server start or code change + -----> Module:callback_mode/0 + gen_statem:stop -----> Module:terminate/3 gen_statem:call @@ -116,9 +119,11 @@ erlang:'!' -----> Module:StateName/3 </p> <p> If a callback function fails or returns a bad value, - the <c>gen_statem</c> terminates. However, an exception of class + the <c>gen_statem</c> terminates, unless otherwise stated. + However, an exception of class <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso> - is not regarded as an error but as a valid return. + is not regarded as an error but as a valid return + from all callback functions. </p> <marker id="state_function"/> <p> @@ -127,7 +132,8 @@ erlang:'!' -----> Module:StateName/3 in a <c>gen_statem</c> is the callback function that is called for all events in this state. It is selected depending on which <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - that the implementation specifies when the server starts. + that the callback module defines with the callback function + <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>. </p> <p> When the @@ -138,9 +144,9 @@ erlang:'!' -----> Module:StateName/3 This gathers all code for a specific state in one function as the <c>gen_statem</c> engine branches depending on state name. - Notice that in this mode the mandatory callback function + Notice the fact that there is a mandatory callback function <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso> - makes the state name <c>terminate</c> unusable. + makes the state name <c>terminate</c> unusable in this mode. </p> <p> When the @@ -249,11 +255,10 @@ erlang:'!' -----> Module:StateName/3 -behaviour(gen_statem). -export([start/0,push/0,get_count/0,stop/0]). --export([terminate/3,code_change/4,init/1]). +-export([terminate/3,code_change/4,init/1,callback_mode/0]). -export([on/3,off/3]). name() -> pushbutton_statem. % The registered server name -callback_mode() -> state_functions. %% API. This example uses a registered name name() %% and does not link to the caller. @@ -270,15 +275,14 @@ stop() -> terminate(_Reason, _State, _Data) -> void. code_change(_Vsn, State, Data, _Extra) -> - {callback_mode(),State,Data}. + {ok,State,Data}. init([]) -> - %% Set the callback mode and initial state + data. - %% Data is used only as a counter. + %% Set the initial state + data. Data is used only as a counter. State = off, Data = 0, - {callback_mode(),State,Data}. - + {ok,State,Data}. +callback_mode() -> state_functions. -%%% State functions +%%% State function(s) off({call,From}, push, Data) -> %% Go to 'on', increment count and reply @@ -326,18 +330,13 @@ ok To compare styles, here follows the same example using <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> <c>state_functions</c>, or rather the code to replace - from function <c>init/1</c> of the <c>pushbutton.erl</c> + after function <c>init/1</c> of the <c>pushbutton.erl</c> example file above: </p> <code type="erl"> -init([]) -> - %% Set the callback mode and initial state + data. - %% Data is used only as a counter. - State = off, Data = 0, - {handle_event_function,State,Data}. - +callback_mode() -> handle_event_function. -%%% Event handling +%%% State function(s) handle_event({call,From}, push, off, Data) -> %% Go to 'on', increment count and reply @@ -426,8 +425,8 @@ handle_event(_, _, State, Data) -> <desc> <p> Debug option that can be used when starting - a <c>gen_statem</c> server through, for example, - <seealso marker="#enter_loop/5"><c>enter_loop/5</c></seealso>. + a <c>gen_statem</c> server through, + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>. </p> <p> For every entry in <c><anno>Dbgs</anno></c>, @@ -525,12 +524,9 @@ handle_event(_, _, State, Data) -> <desc> <p> The <em>callback mode</em> is selected when starting the - <c>gen_statem</c> using the return value from - <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> - or when calling - <seealso marker="#enter_loop/5"><c>enter_loop/5,6,7</c></seealso>, - and with the return value from - <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>. + <c>gen_statem</c> and after code change + using the return value from + <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>. </p> <taglist> <tag><c>state_functions</c></tag> @@ -691,7 +687,7 @@ handle_event(_, _, State, Data) -> <seealso marker="#state_function">state function</seealso>, from <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> or by giving them to - <seealso marker="#enter_loop/6"><c>enter_loop/6,7</c></seealso>. + <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>. </p> <p> Actions are executed in the containing list order. @@ -923,7 +919,8 @@ handle_event(_, _, State, Data) -> </p> <note> <p> - To avoid getting a late reply in the caller's + For <c><anno>Timeout</anno> =/= infinity</c>, + to avoid getting a late reply in the caller's inbox, this function spawns a proxy process that does the call. A late reply gets delivered to the dead proxy process, hence gets discarded. This is @@ -958,35 +955,36 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="5"/> + <name name="enter_loop" arity="4"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> The same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> - except that no + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> + with <c>Actions = []</c> except that no <seealso marker="#type-server_name"><c>server_name()</c></seealso> - must have been registered. + must have been registered. This creates an anonymous server. </p> </desc> </func> <func> - <name name="enter_loop" arity="6"/> + <name name="enter_loop" arity="5"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> If <c><anno>Server_or_Actions</anno></c> is a <c>list()</c>, the same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> except that no <seealso marker="#type-server_name"><c>server_name()</c></seealso> must have been registered and <c>Actions = <anno>Server_or_Actions</anno></c>. + This creates an anonymous server. </p> <p> Otherwise the same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> with <c>Server = <anno>Server_or_Actions</anno></c> and <c>Actions = []</c>. @@ -995,7 +993,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="7"/> + <name name="enter_loop" arity="6"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> @@ -1015,21 +1013,31 @@ handle_event(_, _, State, Data) -> the <c>gen_statem</c> behavior provides. </p> <p> - <c><anno>Module</anno></c>, <c><anno>Opts</anno></c>, and - <c><anno>Server</anno></c> have the same meanings - as when calling + <c><anno>Module</anno></c>, <c><anno>Opts</anno></c> + have the same meaning as when calling <seealso marker="#start_link/3"><c>start[_link]/3,4</c></seealso>. + </p> + <p> + If <c><anno>Server</anno></c> is <c>self()</c> an anonymous + server is created just as when using + <seealso marker="#start_link/3"><c>start[_link]/3</c></seealso>. + If <c><anno>Server</anno></c> is a + <seealso marker="#type-server_name"><c>server_name()</c></seealso> + a named server is created just as when using + <seealso marker="#start_link/4"><c>start[_link]/4</c></seealso>. However, the <seealso marker="#type-server_name"><c>server_name()</c></seealso> name must have been registered accordingly - <em>before</em> this function is called.</p> + <em>before</em> this function is called. + </p> <p> - <c><anno>CallbackMode</anno></c>, <c><anno>State</anno></c>, - <c><anno>Data</anno></c>, and <c><anno>Actions</anno></c> + <c><anno>State</anno></c>, <c><anno>Data</anno></c>, + and <c><anno>Actions</anno></c> have the same meanings as in the return value of <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>. - Also, the callback module <c><anno>Module</anno></c> - does not need to export an <c>init/1</c> function. + Also, the callback module does not need to export a + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + function. </p> <p> The function fails if the calling process was not started by a @@ -1253,6 +1261,48 @@ handle_event(_, _, State, Data) -> <funcs> <func> + <name>Module:callback_mode() -> CallbackMode</name> + <fsummary>Update the internal state during upgrade/downgrade.</fsummary> + <type> + <v> + CallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> + when it needs to find out the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + of the callback module. The value is cached by <c>gen_statem</c> + for efficiency reasons, so this function is only called + once after server start and after code change, + but before the first + <seealso marker="#state_function">state function</seealso> + is called. More occasions may be added in future versions + of <c>gen_statem</c>. + </p> + <p> + Server start happens either when + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + returns or when + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso> + is called. Code change happens when + <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso> + returns. + </p> + <note> + <p> + If this function's body does not consist of solely one of two + possible + <seealso marker="#type-callback_mode">atoms</seealso> + the callback module is doing something strange. + </p> + </note> + </desc> + </func> + + <func> <name>Module:code_change(OldVsn, OldState, OldData, Extra) -> Result </name> @@ -1262,11 +1312,7 @@ handle_event(_, _, State, Data) -> <v> Vsn = term()</v> <v>OldState = NewState = term()</v> <v>Extra = term()</v> - <v>Result = {NewCallbackMode,NewState,NewData} | Reason</v> - <v> - NewCallbackMode = - <seealso marker="#type-callback_mode">callback_mode()</seealso> - </v> + <v>Result = {ok,NewState,NewData} | Reason</v> <v> OldState = NewState = <seealso marker="#type-state">state()</seealso> @@ -1295,21 +1341,6 @@ handle_event(_, _, State, Data) -> <c>Module</c>. If no such attribute is defined, the version is the checksum of the Beam file. </p> - <note> - <p> - If you would dare to change - <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - during release upgrade/downgrade, the upgrade is no problem, - as the new code surely knows what <em>callback mode</em> - it needs. However, for a downgrade this function must - know from argument <c>Extra</c> that comes from the - <seealso marker="sasl:appup"><c>sasl:appup</c></seealso> - file what <em>callback mode</em> the old code did use. - It can also be possible to figure this out - from argument <c>{down,Vsn}</c>, as <c>Vsn</c> - in effect defines the old callback module version. - </p> - </note> <p> <c>OldState</c> and <c>OldData</c> is the internal state of the <c>gen_statem</c>. @@ -1321,31 +1352,32 @@ handle_event(_, _, State, Data) -> <p> If successful, the function must return the updated internal state in an - <c>{NewCallbackMode,NewState,NewData}</c> tuple. + <c>{ok,NewState,NewData}</c> tuple. </p> <p> - If the function returns <c>Reason</c>, the ongoing - upgrade fails and rolls back to the old release.</p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Result</c> or <c>Reason</c>. + If the function returns a failure <c>Reason</c>, the ongoing + upgrade fails and rolls back to the old release. + Note that <c>Reason</c> can not be an <c>{ok,_,_}</c> tuple + since that will be regarded as a + <c>{ok,NewState,NewData}</c> tuple, + and that a tuple matching <c>{ok,_}</c> + is an also invalid failure <c>Reason</c>. + It is recommended to use an atom as <c>Reason</c> since + it will be wrapped in an <c>{error,Reason}</c> tuple. </p> </desc> </func> <func> <name>Module:init(Args) -> Result</name> - <fsummary>Initialize process and internal state.</fsummary> + <fsummary> + Optional function for initializing process and internal state. + </fsummary> <type> <v>Args = term()</v> - <v>Result = {CallbackMode,State,Data}</v> - <v> | {CallbackMode,State,Data,Actions}</v> + <v>Result = {ok,State,Data}</v> + <v> | {ok,State,Data,Actions}</v> <v> | {stop,Reason} | ignore</v> - <v> - CallbackMode = - <seealso marker="#type-callback_mode">callback_mode()</seealso> - </v> <v>State = <seealso marker="#type-state">state()</seealso></v> <v> Data = <seealso marker="#type-data">data()</seealso> @@ -1364,7 +1396,7 @@ handle_event(_, _, State, Data) -> <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> or <seealso marker="#start/3"><c>start/3,4</c></seealso>, - this function is called by the new process to initialize + this optional function is called by the new process to initialize the implementation state and server data. </p> <p> @@ -1373,11 +1405,8 @@ handle_event(_, _, State, Data) -> </p> <p> If the initialization is successful, the function is to - return <c>{CallbackMode,State,Data}</c> or - <c>{CallbackMode,State,Data,Actions}</c>. - <c>CallbackMode</c> selects the - <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - of the <c>gen_statem</c>. + return <c>{ok,State,Data}</c> or + <c>{ok,State,Data,Actions}</c>. <c>State</c> is the initial <seealso marker="#type-state"><c>state()</c></seealso> and <c>Data</c> the initial server @@ -1395,11 +1424,16 @@ handle_event(_, _, State, Data) -> or <c>ignore</c>; see <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Result</c>. - </p> + <note> + <p> + This callback is optional, so a callback module does not need + to export it, but most do. If this function is not exported, + the <c>gen_statem</c> should be started through + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + and + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>. + </p> + </note> </desc> </func> @@ -1430,10 +1464,14 @@ handle_event(_, _, State, Data) -> This callback is optional, so a callback module does not need to export it. The <c>gen_statem</c> module provides a default implementation of this function that returns - <c>{State,Data}</c>. If this callback fails, the default - function returns <c>{State,Info}</c>, - where <c>Info</c> informs of the crash but no details, - to hide possibly sensitive data. + <c>{State,Data}</c>. + </p> + <p> + If this callback is exported but fails, + to hide possibly sensitive data, + the default function will instead return <c>{State,Info}</c>, + where <c>Info</c> says nothing but the fact that + <c>format_status/2</c> has crashed. </p> </note> <p>This function is called by a <c>gen_statem</c> process when @@ -1494,11 +1532,6 @@ handle_event(_, _, State, Data) -> printed in log files. Another use is to hide sensitive data from being written to the error log. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Status</c>. - </p> </desc> </func> @@ -1573,9 +1606,12 @@ handle_event(_, _, State, Data) -> see <seealso marker="#type-action"><c>action()</c></seealso>. </p> <p> - These functions can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso>, - to return the result. + Note the fact that you can use + <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso> + to return the result, which can be useful. + For example to bail out with <c>throw(keep_state_and_data)</c> + from deep within complex code that is in no position to + return <c>{next_state,State,Data}</c>. </p> </desc> </func> @@ -1648,11 +1684,6 @@ handle_event(_, _, State, Data) -> and an error report is issued using <seealso marker="kernel:error_logger#format/2"><c>error_logger:format/2</c></seealso>. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Ignored</c>, which is ignored anyway. - </p> </desc> </func> </funcs> diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index d8fec1147f..f0347703e7 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -526,7 +526,7 @@ </item> <item> <p> - The <c>stdlib</c> reference manual is updated to show + The STDLIB reference manual is updated to show correct information about the return value of <c>gen_fsm:reply/2</c>.</p> <p> @@ -6236,7 +6236,7 @@ documentation for <c>compile</c> on how to provide the key for encrypting, and the documentation for <c>beam_lib</c> on how to provide the key for decryption so that tools such - as the Debugger, <c>xref</c>, or <c>cover</c> can be used.</p> + as the Debugger, Xref, or Cover can be used.</p> <p>The <c>beam_lib:chunks/2</c> functions now accepts an additional chunk type <c>compile_info</c> to retrieve the compilation information directly as a term. (Thanks diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index 58ca5644cf..da03c39a26 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -59,9 +59,9 @@ <p>When a process that is started using <c>proc_lib</c> terminates abnormally (that is, with another exit reason than <c>normal</c>, <c>shutdown</c>, or <c>{shutdown,Term}</c>), a <em>crash report</em> - is generated, which is written to terminal by the default <c>SASL</c> + is generated, which is written to terminal by the default SASL event handler. That is, the crash report is normally only visible - if the <c>SASL</c> application is started; see + if the SASL application is started; see <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso> and section <seealso marker="sasl:error_logging">SASL Error Logging</seealso> in the SASL User's Guide.</p> diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml index a46ca47033..9f3aff03a3 100644 --- a/lib/stdlib/doc/src/queue.xml +++ b/lib/stdlib/doc/src/queue.xml @@ -401,7 +401,7 @@ </func> <func> - <name name="liat" arity="1"/>v + <name name="liat" arity="1"/> <fsummary>Remove the tail item from a queue.</fsummary> <desc> <p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml index cde73269a8..f857cc394b 100644 --- a/lib/stdlib/doc/src/stdlib_app.xml +++ b/lib/stdlib/doc/src/stdlib_app.xml @@ -31,14 +31,14 @@ <app>STDLIB</app> <appsummary>The STDLIB application.</appsummary> <description> - <p>The <c>STDLIB</c> application is mandatory in the sense that the minimal - system based on Erlang/OTP consists of <c>Kernel</c> and <c>STDLIB</c>. - The <c>STDLIB</c> application contains no services.</p> + <p>The STDLIB application is mandatory in the sense that the minimal + system based on Erlang/OTP consists of Kernel and STDLIB. + The STDLIB application contains no services.</p> </description> <section> <title>Configuration</title> - <p>The following configuration parameters are defined for the <c>STDLIB</c> + <p>The following configuration parameters are defined for the STDLIB application. For more information about configuration parameters, see the <seealso marker="kernel:app"><c>app(4)</c></seealso> module in Kernel.</p> diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml index 8f2ce36b06..fcaccdb2cb 100644 --- a/lib/stdlib/doc/src/timer.xml +++ b/lib/stdlib/doc/src/timer.xml @@ -253,7 +253,7 @@ is needed. This is useful during development, but in a target system the server is to be started explicitly. Use configuration parameters for - <seealso marker="kernel:index"><c>Kernel</c></seealso> for this.</p> + <seealso marker="kernel:index">Kernel</seealso> for this.</p> </desc> </func> diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml index 7f79ac88a1..efc8b75075 100644 --- a/lib/stdlib/doc/src/unicode_usage.xml +++ b/lib/stdlib/doc/src/unicode_usage.xml @@ -274,8 +274,8 @@ marker="stdlib:io"><c>io</c></seealso> module, the file handling, the <seealso marker="stdlib:unicode"><c>unicode</c></seealso> module, and - the bit syntax). Today most modules in <c>Kernel</c> and - <c>STDLIB</c>, as well as the VM are Unicode-aware.</p> + the bit syntax). Today most modules in Kernel and + STDLIB, as well as the VM are Unicode-aware.</p> </item> <tag>File I/O</tag> <item> @@ -765,7 +765,7 @@ Eshell V5.10.1 (abort with ^G) file system). The Unicode character list is used to denote filenames or directory names. If the file system content is listed, you also get Unicode lists as return value. The support - lies in the <c>Kernel</c> and <c>STDLIB</c> modules, which is why + lies in the Kernel and STDLIB modules, which is why most applications (that does not explicitly require the filenames to be in the ISO Latin-1 range) benefit from the Unicode support without change.</p> @@ -843,7 +843,7 @@ Eshell V5.10.1 (abort with ^G) <title>Notes About Raw Filenames</title> <marker id="notes-about-raw-filenames"/> <p>Raw filenames were introduced together with Unicode filename support - in <c>ERTS</c> 5.8.2 (Erlang/OTP R14B01). The reason "raw + in ERTS 5.8.2 (Erlang/OTP R14B01). The reason "raw filenames" were introduced in the system was to be able to represent filenames, specified in different encodings on the same system, diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml index de23608046..0b5eac1e16 100644 --- a/lib/stdlib/doc/src/zip.xml +++ b/lib/stdlib/doc/src/zip.xml @@ -138,7 +138,7 @@ <p>File information as in <seealso marker="kernel:file#read_file_info/1"> <c>file:read_file_info/1</c></seealso> - in <c>Kernel</c></p> + in Kernel</p> </item> <tag><c>comment</c></tag> <item> @@ -345,7 +345,7 @@ prepended to filenames when extracting them from the zip archive. (Acting like <seealso marker="kernel:file#set_cwd/1"> - <c>file:set_cwd/1</c></seealso> in <c>Kernel</c>, + <c>file:set_cwd/1</c></seealso> in Kernel, but without changing the global <c>cwd</c> property.)</p> </item> </taglist> @@ -420,7 +420,7 @@ (<c>cwd</c>). This is prepended to filenames when adding them, although not in the zip archive (acting like <seealso marker="kernel:file#set_cwd/1"> - <c>file:set_cwd/1</c></seealso> in <c>Kernel</c>, but without + <c>file:set_cwd/1</c></seealso> in Kernel, but without changing the global <c>cwd</c> property.).</p> </item> <tag><c>{compress, <anno>What</anno>}</c></tag> diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl index fe9df601eb..d7ee5c1f5d 100644 --- a/lib/stdlib/src/beam_lib.erl +++ b/lib/stdlib/src/beam_lib.erl @@ -55,7 +55,7 @@ -type beam() :: module() | file:filename() | binary(). --type forms() :: [erl_parse:abstract_form()]. +-type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()]. -type abst_code() :: {AbstVersion :: atom(), forms()} | 'no_abstract_code'. -type dataB() :: binary(). diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl index 3f74e01692..20de06fd0b 100644 --- a/lib/stdlib/src/ets.erl +++ b/lib/stdlib/src/ets.erl @@ -232,20 +232,20 @@ match(_) -> match_object(_, _) -> erlang:nif_error(undef). --spec match_object(Tab, Pattern, Limit) -> {[Match], Continuation} | +-spec match_object(Tab, Pattern, Limit) -> {[Object], Continuation} | '$end_of_table' when Tab :: tab(), Pattern :: match_pattern(), Limit :: pos_integer(), - Match :: [term()], + Object :: tuple(), Continuation :: continuation(). match_object(_, _, _) -> erlang:nif_error(undef). --spec match_object(Continuation) -> {[Match], Continuation} | +-spec match_object(Continuation) -> {[Object], Continuation} | '$end_of_table' when - Match :: [term()], + Object :: tuple(), Continuation :: continuation(). match_object(_) -> diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 23bddafeed..3b3477b282 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -24,7 +24,7 @@ [start/3,start/4,start_link/3,start_link/4, stop/1,stop/3, cast/2,call/2,call/3, - enter_loop/5,enter_loop/6,enter_loop/7, + enter_loop/4,enter_loop/5,enter_loop/6, reply/1,reply/2]). %% gen callbacks @@ -63,8 +63,8 @@ {To :: pid(), Tag :: term()}. % Reply-to specifier for call -type state() :: - state_name() | % For state callback function StateName/5 - term(). % For state callback function handle_event/5 + state_name() | % For StateName/3 callback functios + term(). % For handle_event/4 callback function -type state_name() :: atom(). @@ -174,28 +174,33 @@ %% an {ok, ...} tuple. Thereafter the state callbacks are called %% for all events to this server. -callback init(Args :: term()) -> - {callback_mode(), state(), data()} | - {callback_mode(), state(), data(), [action()] | action()} | + {ok, state(), data()} | + {ok, state(), data(), [action()] | action()} | 'ignore' | {'stop', Reason :: term()}. -%% Example state callback for callback_mode() =:= state_functions -%% state name 'state_name'. +%% This callback shall return the callback mode of the callback module. %% -%% In this mode all states has to be type state_name() i.e atom(). +%% It is called once after init/0 and code_change/4 but before +%% the first state callback StateName/3 or handle_event/4. +-callback callback_mode() -> callback_mode(). + +%% Example state callback for StateName = 'state_name' +%% when callback_mode() =:= state_functions. +%% +%% In this mode all states has to be of type state_name() i.e atom(). %% -%% Note that state callbacks and only state callbacks have arity 5 -%% and that is intended. +%% Note that the only callbacks that have arity 3 are these +%% StateName/3 callbacks and terminate/3, so the state name +%% 'terminate' is unusable in this mode. -callback state_name( event_type(), EventContent :: term(), Data :: data()) -> state_function_result(). %% -%% State callback for callback_mode() =:= handle_event_function. -%% -%% Note that state callbacks and only state callbacks have arity 5 -%% and that is intended. +%% State callback for all states +%% when callback_mode() =:= handle_event_function. -callback handle_event( event_type(), EventContent :: term(), @@ -219,9 +224,8 @@ OldState :: state(), OldData :: data(), Extra :: term()) -> - {NewCallbackMode :: callback_mode(), - NewState :: state(), - NewData :: data()}. + {ok, NewState :: state(), NewData :: data()} | + (Reason :: term()). %% Format the callback module state in some sensible that is %% often condensed way. For StatusOption =:= 'normal' the perferred @@ -239,10 +243,13 @@ [init/1, % One may use enter_loop/5,6,7 instead format_status/2, % Has got a default implementation %% - state_name/3, % Example for callback_mode =:= state_functions: - %% there has to be a StateName/5 callback function for every StateName. + state_name/3, % Example for callback_mode() =:= state_functions: + %% there has to be a StateName/3 callback function + %% for every StateName in your state machine but the state name + %% 'state_name' does of course not have to be used. %% - handle_event/4]). % For callback_mode =:= handle_event_function + handle_event/4 % For callback_mode() =:= handle_event_function + ]). %% Type validation functions callback_mode(CallbackMode) -> @@ -450,43 +457,35 @@ reply({To,Tag}, Reply) when is_pid(To) -> %% the same arguments as you would have returned from init/1 -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data()) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data) -> - enter_loop(Module, Opts, CallbackMode, State, Data, self()). +enter_loop(Module, Opts, State, Data) -> + enter_loop(Module, Opts, State, Data, self()). %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server_or_Actions :: server_name() | pid() | [action()]) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Actions) -> +enter_loop(Module, Opts, State, Data, Server_or_Actions) -> if is_list(Server_or_Actions) -> - enter_loop( - Module, Opts, CallbackMode, State, Data, - self(), Server_or_Actions); + enter_loop(Module, Opts, State, Data, self(), Server_or_Actions); true -> - enter_loop( - Module, Opts, CallbackMode, State, Data, - Server_or_Actions, []) + enter_loop(Module, Opts, State, Data, Server_or_Actions, []) end. %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server :: server_name() | pid(), Actions :: [action()] | action()) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data, Server, Actions) -> +enter_loop(Module, Opts, State, Data, Server, Actions) -> is_atom(Module) orelse error({atom,Module}), - callback_mode(CallbackMode) orelse error({callback_mode,CallbackMode}), Parent = gen:get_parent(), - enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent). + enter(Module, Opts, State, Data, Server, Actions, Parent). %%--------------------------------------------------------------------------- %% API helpers @@ -514,7 +513,7 @@ send(Proc, Msg) -> end. %% Here the init_it/6 and enter_loop/5,6,7 functions converge -enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) -> +enter(Module, Opts, State, Data, Server, Actions, Parent) -> %% The values should already have been type checked Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Opts), @@ -530,7 +529,7 @@ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) -> [Actions,{postpone,false}] end, S = #{ - callback_mode => CallbackMode, + callback_mode => undefined, module => Module, name => Name, %% All fields below will be replaced according to the arguments to @@ -558,9 +557,15 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> Result -> init_result(Starter, Parent, ServerRef, Module, Result, Opts); Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + Name = gen:get_proc_name(ServerRef), gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), - erlang:raise(Class, Reason, erlang:get_stacktrace()) + error_info( + Class, Reason, Stacktrace, + #{name => Name, callback_mode => undefined}, + [], [], undefined), + erlang:raise(Class, Reason, Stacktrace) end. %%--------------------------------------------------------------------------- @@ -568,30 +573,12 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> case Result of - {CallbackMode,State,Data} -> - case callback_mode(CallbackMode) of - true -> - proc_lib:init_ack(Starter, {ok,self()}), - enter( - Module, Opts, CallbackMode, State, Data, - ServerRef, [], Parent); - false -> - Error = {callback_mode,CallbackMode}, - proc_lib:init_ack(Starter, {error,Error}), - exit(Error) - end; - {CallbackMode,State,Data,Actions} -> - case callback_mode(CallbackMode) of - true -> - proc_lib:init_ack(Starter, {ok,self()}), - enter( - Module, Opts, CallbackMode, State, Data, - ServerRef, Actions, Parent); - false -> - Error = {callback_mode,CallbackMode}, - proc_lib:init_ack(Starter, {error,Error}), - exit(Error) - end; + {ok,State,Data} -> + proc_lib:init_ack(Starter, {ok,self()}), + enter(Module, Opts, State, Data, ServerRef, [], Parent); + {ok,State,Data,Actions} -> + proc_lib:init_ack(Starter, {ok,self()}), + enter(Module, Opts, State, Data, ServerRef, Actions, Parent); {stop,Reason} -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), @@ -601,8 +588,14 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> proc_lib:init_ack(Starter, ignore), exit(normal); _ -> - Error = {bad_return_value,Result}, + Name = gen:get_proc_name(ServerRef), + gen:unregister_name(ServerRef), + Error = {bad_return_from_init,Result}, proc_lib:init_ack(Starter, {error,Error}), + error_info( + error, Error, ?STACKTRACE(), + #{name => Name, callback_mode => undefined}, + [], [], undefined), exit(Error) end. @@ -630,11 +623,9 @@ system_code_change( Result -> Result end of - {NewCallbackMode,NewState,NewData} -> - callback_mode(NewCallbackMode) orelse - error({callback_mode,NewCallbackMode}), + {ok,NewState,NewData} -> {ok, - S#{callback_mode := NewCallbackMode, + S#{callback_mode := undefined, state := NewState, data := NewData}}; {ok,_} = Error -> @@ -675,14 +666,14 @@ format_status( %% them, not as the real erlang messages. Use trace for that. %%--------------------------------------------------------------------------- -print_event(Dev, {in,Event}, {Name,_}) -> +print_event(Dev, {in,Event}, {Name,State}) -> io:format( - Dev, "*DBG* ~p received ~s~n", - [Name,event_string(Event)]); -print_event(Dev, {out,Reply,{To,_Tag}}, {Name,_}) -> + Dev, "*DBG* ~p receive ~s in state ~p~n", + [Name,event_string(Event),State]); +print_event(Dev, {out,Reply,{To,_Tag}}, {Name,State}) -> io:format( - Dev, "*DBG* ~p sent ~p to ~p~n", - [Name,Reply,To]); + Dev, "*DBG* ~p send ~p to ~p from state ~p~n", + [Name,Reply,To,State]); print_event(Dev, {Tag,Event,NextState}, {Name,State}) -> StateString = case NextState of @@ -874,22 +865,36 @@ loop_event( %% try case CallbackMode of + undefined -> + Module:callback_mode(); state_functions -> - Module:State(Type, Content, Data); + erlang:apply(Module, State, [Type,Content,Data]); handle_event_function -> Module:handle_event(Type, Content, State, Data) - end of + end + of + Result when CallbackMode =:= undefined -> + loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) catch + Result when CallbackMode =:= undefined -> + loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result); - error:badarg when CallbackMode =:= state_functions -> + error:badarg -> case erlang:get_stacktrace() of - [{erlang,apply,[Module,State,_],_}|Stacktrace] -> - Args = [Type,Content,Data], + [{erlang,apply, + [Module,State,[Type,Content,Data]=Args], + _} + |Stacktrace] + when CallbackMode =:= state_functions -> + %% We get here e.g if apply fails + %% due to State not being an atom terminate( error, {undef_state_function,{Module,State,Args}}, @@ -901,24 +906,29 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end; error:undef -> - %% Process an undef to check for the simple mistake + %% Process undef to check for the simple mistake %% of calling a nonexistent state function + %% to make the undef more precise case erlang:get_stacktrace() of - [{Module,State, - [Type,Content,Data]=Args, - _} + [{Module,callback_mode,[]=Args,_} + |Stacktrace] + when CallbackMode =:= undefined -> + terminate( + error, + {undef_callback,{Module,callback_mode,Args}}, + Stacktrace, + Debug, S, [Event|Events], State, Data, P); + [{Module,State,[Type,Content,Data]=Args,_} |Stacktrace] - when CallbackMode =:= state_functions -> + when CallbackMode =:= state_functions -> terminate( error, {undef_state_function,{Module,State,Args}}, Stacktrace, Debug, S, [Event|Events], State, Data, P); - [{Module,handle_event, - [Type,Content,State,Data]=Args, - _} + [{Module,handle_event,[Type,Content,State,Data]=Args,_} |Stacktrace] - when CallbackMode =:= handle_event_function -> + when CallbackMode =:= handle_event_function -> terminate( error, {undef_state_function,{Module,handle_event,Args}}, @@ -936,6 +946,25 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end. +%% Interpret callback_mode() result +loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, CallbackMode) -> + case callback_mode(CallbackMode) of + true -> + Hibernate = false, % We have already GC:ed recently + loop_event( + Parent, Debug, + S#{callback_mode := CallbackMode}, + Events, + State, Data, P, Event, Hibernate); + false -> + terminate( + error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE(), + Debug, S, [Event|Events], State, Data, P) + end. + %% Interpret all callback return variants loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) -> @@ -988,7 +1017,9 @@ loop_event_result( State, Data, P, Event, State, Actions); _ -> terminate( - error, {bad_return_value,Result}, ?STACKTRACE(), + error, + {bad_return_from_state_function,Result}, + ?STACKTRACE(), Debug, S, [Event|Events], State, Data, P) end. @@ -1025,20 +1056,26 @@ loop_event_actions( Postpone, Hibernate, Timeout, NextEvents); false -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; {next_event,Type,Content} -> case event_type(Type) of true -> + NewDebug = + sys_debug(Debug, S, State, {in,{Type,Content}}), loop_event_actions( - Parent, Debug, S, Events, + Parent, NewDebug, S, Events, State, NewData, P, Event, NextState, Actions, Postpone, Hibernate, Timeout, [{Type,Content}|NextEvents]); false -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; %% Actions that set options @@ -1049,7 +1086,9 @@ loop_event_actions( NewPostpone, Hibernate, Timeout, NextEvents); {postpone,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); postpone -> loop_event_actions( @@ -1063,7 +1102,9 @@ loop_event_actions( Postpone, NewHibernate, Timeout, NextEvents); {hibernate,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); hibernate -> loop_event_actions( @@ -1082,7 +1123,9 @@ loop_event_actions( Postpone, Hibernate, NewTimeout, NextEvents); {timeout,_,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); infinity -> % Clear timer - it will never trigger loop_event_actions( @@ -1097,7 +1140,9 @@ loop_event_actions( Postpone, Hibernate, NewTimeout, NextEvents); _ -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; %% @@ -1169,7 +1214,9 @@ do_reply_then_terminate( NewDebug, S, Q, State, Data, P, Rs); _ -> terminate( - error, {bad_action,R}, ?STACKTRACE(), + error, + {bad_reply_action_from_state_function,R}, + ?STACKTRACE(), Debug, S, Q, State, Data, P) end. @@ -1188,8 +1235,9 @@ terminate( C:R -> ST = erlang:get_stacktrace(), error_info( - C, R, ST, Debug, S, Q, P, + C, R, ST, S, Q, P, format_status(terminate, get(), S, State, Data)), + sys:print_log(Debug), erlang:raise(C, R, ST) end, case Reason of @@ -1198,8 +1246,9 @@ terminate( {shutdown,_} -> ok; _ -> error_info( - Class, Reason, Stacktrace, Debug, S, Q, P, - format_status(terminate, get(), S, State, Data)) + Class, Reason, Stacktrace, S, Q, P, + format_status(terminate, get(), S, State, Data)), + sys:print_log(Debug) end, case Stacktrace of [] -> @@ -1209,7 +1258,7 @@ terminate( end. error_info( - Class, Reason, Stacktrace, Debug, + Class, Reason, Stacktrace, #{name := Name, callback_mode := CallbackMode}, Q, P, FmtData) -> {FixedReason,FixedStacktrace} = @@ -1276,9 +1325,7 @@ error_info( case FixedStacktrace of [] -> []; _ -> [FixedStacktrace] - end), - sys:print_log(Debug), - ok. + end). %% Call Module:format_status/2 or return a default value @@ -1291,7 +1338,7 @@ format_status(Opt, PDict, #{module := Module}, State, Data) -> _:_ -> format_status_default( Opt, State, - "Module:format_status/2 crashed") + atom_to_list(Module) ++ ":format_status/2 crashed") end; false -> format_status_default(Opt, State, Data) @@ -1299,10 +1346,10 @@ format_status(Opt, PDict, #{module := Module}, State, Data) -> %% The default Module:format_status/2 format_status_default(Opt, State, Data) -> - SSD = {State,Data}, + StateData = {State,Data}, case Opt of terminate -> - SSD; + StateData; _ -> - [{data,[{"State",SSD}]}] + [{data,[{"State",StateData}]}] end. diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl index f8ba6f18e9..340cc21390 100644 --- a/lib/stdlib/src/zip.erl +++ b/lib/stdlib/src/zip.erl @@ -279,7 +279,8 @@ do_openzip_get(F, #openzip{files = Files, in = In0, input = Input, case file_name_search(F, Files) of {#zip_file{offset = Offset},_}=ZFile -> In1 = Input({seek, bof, Offset}, In0), - case get_z_file(In1, Z, Input, Output, [], fun silent/1, CWD, ZFile) of + case get_z_file(In1, Z, Input, Output, [], fun silent/1, + CWD, ZFile, fun all/1) of {file, R, _In2} -> {ok, R}; _ -> throw(file_not_found) end; @@ -1403,9 +1404,10 @@ get_z_files([{#zip_file{offset = Offset},_} = ZFile | Rest], Z, In0, true -> In1 = Input({seek, bof, Offset}, In0), {In2, Acc1} = - case get_z_file(In1, Z, Input, Output, OpO, FB, CWD, ZFile) of + case get_z_file(In1, Z, Input, Output, OpO, FB, + CWD, ZFile, Filter) of {file, GZD, Inx} -> {Inx, [GZD | Acc0]}; - {dir, Inx} -> {Inx, Acc0} + {_, Inx} -> {Inx, Acc0} end, get_z_files(Rest, Z, In2, Opts, Acc1); _ -> @@ -1413,7 +1415,8 @@ get_z_files([{#zip_file{offset = Offset},_} = ZFile | Rest], Z, In0, end. %% get a file from the archive, reading chunks -get_z_file(In0, Z, Input, Output, OpO, FB, CWD, {ZipFile,Extra}) -> +get_z_file(In0, Z, Input, Output, OpO, FB, + CWD, {ZipFile,Extra}, Filter) -> case Input({read, ?LOCAL_FILE_HEADER_SZ}, In0) of {eof, In1} -> {eof, In1}; @@ -1433,29 +1436,64 @@ get_z_file(In0, Z, Input, Output, OpO, FB, CWD, {ZipFile,Extra}) -> end, {BFileN, In3} = Input({read, FileNameLen + ExtraLen}, In1), {FileName, _} = get_file_name_extra(FileNameLen, ExtraLen, BFileN), - FileName1 = add_cwd(CWD, FileName), - case lists:last(FileName) of - $/ -> - %% perhaps this should always be done? - Output({ensure_dir,FileName1},[]), - {dir, In3}; - _ -> - %% FileInfo = local_file_header_to_file_info(LH) - %%{Out, In4, CRC, UncompSize} = - {Out, In4, CRC, _UncompSize} = - get_z_data(CompMethod, In3, FileName1, - CompSize, Input, Output, OpO, Z), - In5 = skip_z_data_descriptor(GPFlag, Input, In4), - %% TODO This should be fixed some day: - %% In5 = Input({set_file_info, FileName, FileInfo#file_info{size=UncompSize}}, In4), - FB(FileName), - CRC =:= CRC32 orelse throw({bad_crc, FileName}), - {file, Out, In5} + ReadAndWrite = + case check_valid_location(CWD, FileName) of + {true,FileName1} -> + true; + {false,FileName1} -> + Filter({ZipFile#zip_file{name = FileName1},Extra}) + end, + case ReadAndWrite of + true -> + case lists:last(FileName) of + $/ -> + %% perhaps this should always be done? + Output({ensure_dir,FileName1},[]), + {dir, In3}; + _ -> + %% FileInfo = local_file_header_to_file_info(LH) + %%{Out, In4, CRC, UncompSize} = + {Out, In4, CRC, _UncompSize} = + get_z_data(CompMethod, In3, FileName1, + CompSize, Input, Output, OpO, Z), + In5 = skip_z_data_descriptor(GPFlag, Input, In4), + %% TODO This should be fixed some day: + %% In5 = Input({set_file_info, FileName, + %% FileInfo#file_info{size=UncompSize}}, In4), + FB(FileName), + CRC =:= CRC32 orelse throw({bad_crc, FileName}), + {file, Out, In5} + end; + false -> + {ignore, In3} end; _ -> throw(bad_local_file_header) end. +%% make sure FileName doesn't have relative path that points over CWD +check_valid_location(CWD, FileName) -> + %% check for directory traversal exploit + case check_dir_level(filename:split(FileName), 0) of + {FileOrDir,Level} when Level < 0 -> + CWD1 = if CWD == "" -> "./"; + true -> CWD + end, + error_logger:format("Illegal path: ~ts, extracting in ~ts~n", + [add_cwd(CWD,FileName),CWD1]), + {false,add_cwd(CWD, FileOrDir)}; + _ -> + {true,add_cwd(CWD, FileName)} + end. + +check_dir_level([FileOrDir], Level) -> + {FileOrDir,Level}; +check_dir_level(["." | Parts], Level) -> + check_dir_level(Parts, Level); +check_dir_level([".." | Parts], Level) -> + check_dir_level(Parts, Level-1); +check_dir_level([_Dir | Parts], Level) -> + check_dir_level(Parts, Level+1). get_file_name_extra(FileNameLen, ExtraLen, B) -> case B of diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 364314f91b..1d1417c2e6 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -37,33 +37,32 @@ all() -> {group, stop_handle_event}, {group, abnormal}, {group, abnormal_handle_event}, - shutdown, stop_and_reply, event_order, + shutdown, stop_and_reply, event_order, code_change, {group, sys}, hibernate, enter_loop]. groups() -> - [{start, [], - [start1, start2, start3, start4, start5, start6, start7, - start8, start9, start10, start11, start12, next_events]}, - {start_handle_event, [], - [start1, start2, start3, start4, start5, start6, start7, - start8, start9, start10, start11, start12, next_events]}, - {stop, [], - [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, - {stop_handle_event, [], - [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, - {abnormal, [], [abnormal1, abnormal2]}, - {abnormal_handle_event, [], [abnormal1, abnormal2]}, - {sys, [], - [sys1, code_change, - call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]}, - {sys_handle_event, [], - [sys1, - call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]}]. + [{start, [], tcs(start)}, + {start_handle_event, [], tcs(start)}, + {stop, [], tcs(stop)}, + {stop_handle_event, [], tcs(stop)}, + {abnormal, [], tcs(abnormal)}, + {abnormal_handle_event, [], tcs(abnormal)}, + {sys, [], tcs(sys)}, + {sys_handle_event, [], tcs(sys)}]. + +tcs(start) -> + [start1, start2, start3, start4, start5, start6, start7, + start8, start9, start10, start11, start12, next_events]; +tcs(stop) -> + [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]; +tcs(abnormal) -> + [abnormal1, abnormal2]; +tcs(sys) -> + [sys1, call_format_status, + error_format_status, terminate_crash_format, + get_state, replace_state]. + init_per_suite(Config) -> Config. @@ -461,10 +460,10 @@ abnormal2(Config) -> {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), %% bad return value in the gen_statem loop - {{bad_return_value,badreturn},_} = + {{bad_return_from_state_function,badreturn},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), receive - {'EXIT',Pid,{bad_return_value,badreturn}} -> ok + {'EXIT',Pid,{bad_return_from_state_function,badreturn}} -> ok after 5000 -> ct:fail(gen_statem_did_not_die) end, @@ -633,11 +632,13 @@ sys1(Config) -> sys:resume(Pid), stop_it(Pid). -code_change(Config) -> - Mode = handle_event_function, - {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), +code_change(_Config) -> + {ok,Pid} = + gen_statem:start( + ?MODULE, {callback_mode,state_functions,[]}, []), {idle,data} = sys:get_state(Pid), sys:suspend(Pid), + Mode = handle_event_function, sys:change_code(Pid, ?MODULE, old_vsn, Mode), sys:resume(Pid), {idle,{old_vsn,data,Mode}} = sys:get_state(Pid), @@ -708,7 +709,7 @@ error_format_status(Config) -> gen_statem:start( ?MODULE, start_arg(Config, {data,Data}), []), %% bad return value in the gen_statem loop - {{bad_return_value,badreturn},_} = + {{bad_return_from_state_function,badreturn},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), receive {error,_, @@ -716,7 +717,7 @@ error_format_status(Config) -> "** State machine"++_, [Pid,{{call,_},badreturn}, {formatted,idle,Data}, - error,{bad_return_value,badreturn}|_]}} -> + error,{bad_return_from_state_function,badreturn}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> error_logger_forwarder:unregister(), @@ -1029,11 +1030,7 @@ enter_loop(_Config) -> end, %% Process not started using proc_lib - CallbackMode = state_functions, - Pid4 = - spawn_link( - gen_statem, enter_loop, - [?MODULE,[],CallbackMode,state0,[]]), + Pid4 = spawn_link(gen_statem, enter_loop, [?MODULE,[],state0,[]]), receive {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> ok @@ -1107,21 +1104,18 @@ enter_loop(Reg1, Reg2) -> anon -> ignore end, proc_lib:init_ack({ok, self()}), - CallbackMode = state_functions, case Reg2 of local -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], {local,armitage}); + ?MODULE, [], state0, [], {local,armitage}); global -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], {global,armitage}); + ?MODULE, [], state0, [], {global,armitage}); via -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], - {via, dummy_via, armitage}); + ?MODULE, [], state0, [], {via, dummy_via, armitage}); anon -> - gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, []) + gen_statem:enter_loop(?MODULE, [], state0, []) end. @@ -1266,33 +1260,39 @@ init(stop_shutdown) -> {stop,shutdown}; init(sleep) -> ?t:sleep(1000), - {state_functions,idle,data}; + {ok,idle,data}; init(hiber) -> - {state_functions,hiber_idle,[]}; + {ok,hiber_idle,[]}; init(hiber_now) -> - {state_functions,hiber_idle,[],[hibernate]}; + {ok,hiber_idle,[],[hibernate]}; init({data, Data}) -> - {state_functions,idle,Data}; + {ok,idle,Data}; init({callback_mode,CallbackMode,Arg}) -> - case init(Arg) of - {_,State,Data,Ops} -> - {CallbackMode,State,Data,Ops}; - {_,State,Data} -> - {CallbackMode,State,Data}; - Other -> - Other - end; + ets:new(?MODULE, [named_table,private]), + ets:insert(?MODULE, {callback_mode,CallbackMode}), + init(Arg); init({map_statem,#{init := Init}=Machine}) -> + ets:new(?MODULE, [named_table,private]), + ets:insert(?MODULE, {callback_mode,handle_event_function}), case Init() of {ok,State,Data,Ops} -> - {handle_event_function,State,[Data|Machine],Ops}; + {ok,State,[Data|Machine],Ops}; {ok,State,Data} -> - {handle_event_function,State,[Data|Machine]}; + {ok,State,[Data|Machine]}; Other -> Other end; init([]) -> - {state_functions,idle,data}. + {ok,idle,data}. + +callback_mode() -> + try ets:lookup(?MODULE, callback_mode) of + [{callback_mode,CallbackMode}] -> + CallbackMode + catch + error:badarg -> + state_functions + end. terminate(_, _State, crash_terminate) -> exit({crash,terminate}); @@ -1568,7 +1568,12 @@ wrap_result(Result) -> code_change(OldVsn, State, Data, CallbackMode) -> - {CallbackMode,State,{OldVsn,Data,CallbackMode}}. + io:format( + "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), + ets:insert(?MODULE, {callback_mode,CallbackMode}), + io:format( + "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), + {ok,State,{OldVsn,Data,CallbackMode}}. format_status(terminate, [_Pdict,State,Data]) -> {formatted,State,Data}; diff --git a/lib/stdlib/test/zip_SUITE.erl b/lib/stdlib/test/zip_SUITE.erl index 2add5a39a2..7d90795c9e 100644 --- a/lib/stdlib/test/zip_SUITE.erl +++ b/lib/stdlib/test/zip_SUITE.erl @@ -25,6 +25,7 @@ zip_to_binary/1, unzip_options/1, zip_options/1, list_dir_options/1, aliases/1, openzip_api/1, zip_api/1, open_leak/1, unzip_jar/1, + unzip_traversal_exploit/1, compress_control/1, foldl/1]). @@ -38,7 +39,8 @@ all() -> [borderline, atomic, bad_zip, unzip_from_binary, unzip_to_binary, zip_to_binary, unzip_options, zip_options, list_dir_options, aliases, openzip_api, - zip_api, open_leak, unzip_jar, compress_control, foldl]. + zip_api, open_leak, unzip_jar, compress_control, foldl, + unzip_traversal_exploit]. groups() -> []. @@ -377,6 +379,52 @@ unzip_options(Config) when is_list(Config) -> 0 = delete_files([Subdir]), ok. +%% Test that unzip handles directory traversal exploit (OTP-13633) +unzip_traversal_exploit(Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + ZipName = filename:join(DataDir, "exploit.zip"), + + %% $ zipinfo -1 test/zip_SUITE_data/exploit.zip + %% clash.txt + %% ../clash.txt + %% ../above.txt + %% subdir/../in_root_dir.txt + + %% create a temp directory + SubDir = filename:join(PrivDir, "exploit_test"), + ok = file:make_dir(SubDir), + + ClashFile = filename:join(SubDir,"clash.txt"), + AboveFile = filename:join(SubDir,"above.txt"), + RelativePathFile = filename:join(SubDir,"subdir/../in_root_dir.txt"), + + %% unzip in SubDir + {ok, [ClashFile, ClashFile, AboveFile, RelativePathFile]} = + zip:unzip(ZipName, [{cwd,SubDir}]), + + {ok,<<"This file will overwrite other file.\n">>} = + file:read_file(ClashFile), + {ok,_} = file:read_file(AboveFile), + {ok,_} = file:read_file(RelativePathFile), + + %% clean up + delete_files([SubDir]), + + %% create the temp directory again + ok = file:make_dir(SubDir), + + %% unzip in SubDir + {ok, [ClashFile, AboveFile, RelativePathFile]} = + zip:unzip(ZipName, [{cwd,SubDir},keep_old_files]), + + {ok,<<"This is the original file.\n">>} = + file:read_file(ClashFile), + + %% clean up + delete_files([SubDir]), + ok. + %% Test unzip a jar file (OTP-7382). unzip_jar(Config) when is_list(Config) -> DataDir = proplists:get_value(data_dir, Config), diff --git a/lib/stdlib/test/zip_SUITE_data/exploit.zip b/lib/stdlib/test/zip_SUITE_data/exploit.zip Binary files differnew file mode 100644 index 0000000000..afb8dbd192 --- /dev/null +++ b/lib/stdlib/test/zip_SUITE_data/exploit.zip diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 41037b8f53..c74343d9ca 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.0.1 +STDLIB_VSN = 3.1 diff --git a/lib/syntax_tools/doc/specs/.gitignore b/lib/syntax_tools/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/syntax_tools/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/syntax_tools/doc/src/Makefile b/lib/syntax_tools/doc/src/Makefile index ff4f3f78ff..e55222e59c 100644 --- a/lib/syntax_tools/doc/src/Makefile +++ b/lib/syntax_tools/doc/src/Makefile @@ -81,10 +81,15 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) + +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- XML_FLAGS += +SPECS_FLAGS = -I../../include DVIPS_FLAGS += # ---------------------------------------------------- @@ -93,7 +98,7 @@ DVIPS_FLAGS += $(HTMLDIR)/%.gif: %.gif $(INSTALL_DATA) $< $@ -docs: pdf html man +docs: man pdf html $(TOP_PDF_FILE): $(XML_FILES) @@ -120,6 +125,7 @@ clean clean_docs: rm -f $(MAN3DIR)/* rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECDIR)/* rm -f errs core *~ # ---------------------------------------------------- diff --git a/lib/syntax_tools/doc/src/specs.xml b/lib/syntax_tools/doc/src/specs.xml new file mode 100644 index 0000000000..04c3a494e7 --- /dev/null +++ b/lib/syntax_tools/doc/src/specs.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_epp_dodger.xml"/> + <xi:include href="../specs/specs_erl_comment_scan.xml"/> + <xi:include href="../specs/specs_erl_prettypr.xml"/> + <xi:include href="../specs/specs_erl_recomment.xml"/> + <xi:include href="../specs/specs_erl_syntax.xml"/> + <xi:include href="../specs/specs_erl_syntax_lib.xml"/> + <xi:include href="../specs/specs_erl_tidy.xml"/> + <xi:include href="../specs/specs_igor.xml"/> + <xi:include href="../specs/specs_merl.xml"/> + <xi:include href="../specs/specs_merl_transform.xml"/> + <xi:include href="../specs/specs_prettypr.xml"/> +</specs> diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index 03429d4d42..b5ac564146 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -30,8 +30,14 @@ %% ===================================================================== --type comment() :: {integer(), integer(), integer(), [string()]}. --type commentLine() :: {integer(), integer(), integer(), string()}. +-type comment() :: {Line:: integer(), + Column:: integer(), + Indentation :: integer(), + Text :: [string()]}. +-type commentLine() :: {Line :: integer(), + Column :: integer(), + Indent :: integer(), + Text :: string()}. %% ===================================================================== %% @spec file(FileName::file:filename()) -> [Comment] diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index f1615b2610..df0d78031c 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -195,10 +195,16 @@ format(Node) -> %% ===================================================================== %% @spec format(Tree::syntaxTree(), Options::[term()]) -> string() -%% syntaxTree() = erl_syntax:syntaxTree() %% -%% @type hook() = (syntaxTree(), context(), Continuation) -> document() -%% Continuation = (syntaxTree(), context()) -> document(). +%% @type syntaxTree() = erl_syntax:syntaxTree(). +%% +%% An abstract syntax tree. See the {@link erl_syntax} module for +%% details. +%% +%% @type hook() = (syntaxTree(), context(), Continuation) -> +%% prettypr:document() +%% Continuation = (syntaxTree(), context()) -> +%% prettypr:document(). %% %% A call-back function for user-controlled formatting. See {@link %% format/2}. @@ -277,7 +283,7 @@ format(Node, Options) -> %% ===================================================================== -%% @spec best(Tree::syntaxTree()) -> empty | document() +%% @spec best(Tree::syntaxTree()) -> empty | prettypr:document() %% @equiv best(Tree, []) -spec best(erl_syntax:syntaxTree()) -> 'empty' | prettypr:document(). @@ -288,7 +294,7 @@ best(Node) -> %% ===================================================================== %% @spec best(Tree::syntaxTree(), Options::[term()]) -> -%% empty | document() +%% empty | prettypr:document() %% %% @doc Creates a fixed "best" abstract layout for a syntax tree. This %% is similar to the `layout/2' function, except that here, the final @@ -310,7 +316,7 @@ best(Node, Options) -> %% ===================================================================== -%% @spec layout(Tree::syntaxTree()) -> document() +%% @spec layout(Tree::syntaxTree()) -> prettypr:document() %% @equiv layout(Tree, []) -spec layout(erl_syntax:syntaxTree()) -> prettypr:document(). @@ -320,8 +326,7 @@ layout(Node) -> %% ===================================================================== -%% @spec layout(Tree::syntaxTree(), Options::[term()]) -> document() -%% document() = prettypr:document() +%% @spec layout(Tree::syntaxTree(), Options::[term()]) -> prettypr:document() %% %% @doc Creates an abstract document layout for a syntax tree. The %% result represents a set of possible layouts (cf. module `prettypr'). diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index c1141b2bc6..1d23034991 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -30,6 +30,9 @@ -export([recomment_forms/2, quick_recomment_forms/2, recomment_tree/2]). +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. + %% ===================================================================== %% @spec quick_recomment_forms(Forms, Comments::[Comment]) -> %% syntaxTree() @@ -55,7 +58,6 @@ quick_recomment_forms(Tree, Cs) -> %% ===================================================================== %% @spec recomment_forms(Forms, Comments::[Comment]) -> syntaxTree() %% -%% syntaxTree() = erl_syntax:syntaxTree() %% Forms = syntaxTree() | [syntaxTree()] %% Comment = {Line, Column, Indentation, Text} %% Line = integer() diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 9815559779..5aecf5d774 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -280,7 +280,7 @@ mapfoldl(_, S, []) -> %% ===================================================================== %% @spec variables(syntaxTree()) -> set(atom()) %% -%% set(T) = //stdlib/sets:set(T) +%% @type set(T) = //stdlib/sets:set(T) %% %% @doc Returns the names of variables occurring in a syntax tree, The %% result is a set of variable names represented by atoms. Macro names @@ -1955,7 +1955,7 @@ analyze_application(Node) -> %% ===================================================================== -%% @spec analyze_type_application(Node::syntaxTree()) -> typeName() +%% @spec analyze_type_application(Node::syntaxTree()) -> TypeName %% %% TypeName = {atom(), integer()} %% | {ModuleName, {atom(), integer()}} diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index f2de12b410..9e273dfb84 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -36,6 +36,11 @@ %% been reasonably well tested, but the possibility of errors remains. %% Keep backups of your original code safely stored, until you feel %% confident that the new, modified code can be trusted. +%% +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. +%% +%% @type filename() = file:filename(). -module(erl_tidy). @@ -79,7 +84,6 @@ dir(Dir) -> %% ===================================================================== %% @spec dir(Directory::filename(), Options::[term()]) -> ok -%% filename() = file:filename() %% %% @doc Tidies Erlang source files in a directory and its %% subdirectories. @@ -206,7 +210,7 @@ file__defaults() -> {verbose, false}]. default_printer() -> - fun (Tree, Options) -> erl_prettypr:format(Tree, Options) end. + fun erl_prettypr:format/2. %% ===================================================================== %% @spec file(Name) -> ok @@ -253,7 +257,7 @@ file(Name) -> %% %% <dt>{printer, Function}</dt> %% <dd><ul> -%% <li>`Function = (syntaxTree()) -> string()'</li> +%% <li>`Function = (syntaxTree(), [term()]) -> string()'</li> %% </ul> %% %% Specifies a function for prettyprinting Erlang syntax trees. @@ -414,7 +418,7 @@ write_module(Tree, Name, Opts) -> print_module(Tree, Opts) -> Printer = proplists:get_value(printer, Opts), - io:format(Printer(Tree, Opts)). + io:put_chars(Printer(Tree, Opts)). output(FD, Printer, Tree, Opts) -> io:put_chars(FD, Printer(Tree, Opts)), @@ -513,7 +517,6 @@ module(Forms) -> %% @spec module(Forms, Options::[term()]) -> syntaxTree() %% %% Forms = syntaxTree() | [syntaxTree()] -%% syntaxTree() = erl_syntax:syntaxTree() %% %% @doc Tidies a syntax tree representation of a module %% definition. The given `Forms' may be either a single diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 1d14bd7c3a..943250e5cd 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -151,7 +151,8 @@ default_printer(Tree, Options) -> %% @spec parse_transform(Forms::[syntaxTree()], Options::[term()]) -> %% [syntaxTree()] %% -%% syntaxTree() = erl_syntax:syntaxTree() +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. %% %% @doc Allows Igor to work as a component of the Erlang compiler. %% Including the term `{parse_transform, igor}' in the @@ -212,7 +213,7 @@ merge(Name, Files) -> %% @spec merge(Name::atom(), Files::[filename()], Options::[term()]) -> %% [filename()] %% -%% filename() = file:filename() +%% @type filename() = file:filename() %% %% @doc Merges source code files to a single file. `Name' %% specifies the name of the resulting module - not the name of the @@ -367,6 +368,7 @@ merge_files(Name, Files, Options) -> %% @spec merge_files(Name::atom(), Sources::[Forms], %% Files::[filename()], Options::[term()]) -> %% {syntaxTree(), [stubDescriptor()]} +%% %% Forms = syntaxTree() | [syntaxTree()] %% %% @doc Merges source code files and syntax trees to a single syntax diff --git a/lib/syntax_tools/src/merl_tests.erl b/lib/syntax_tools/src/merl_tests.erl index c1aae3100e..27db594050 100644 --- a/lib/syntax_tools/src/merl_tests.erl +++ b/lib/syntax_tools/src/merl_tests.erl @@ -48,6 +48,21 @@ parse_error_test_() -> f(merl:quote("{"))) ]. +transform_parse_error_test_() -> + [?_assertEqual("merl:quote(\"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(\"{\")")], []))), + ?_assertEqual("merl:quote(2, \"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(2, \"{\")")], []))), + ?_assertEqual("merl:qquote(\"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(\"{\", [{var, V}])")], []))), + ?_assertEqual("merl:qquote(2, \"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(2, \"{\", [{var, V}])")], []))) + ]. + term_test_() -> [?_assertEqual(tuple, erl_syntax:type(merl:term({}))), ?_assertEqual("{foo, 42}", f(merl:term({foo, 42}))) diff --git a/lib/syntax_tools/src/merl_transform.erl b/lib/syntax_tools/src/merl_transform.erl index fe58b6a122..497baddd0a 100644 --- a/lib/syntax_tools/src/merl_transform.erl +++ b/lib/syntax_tools/src/merl_transform.erl @@ -104,10 +104,15 @@ expand_qquote([Text, Env], T, Line) -> case erl_syntax:is_literal(Text) of true -> As = [Line, erl_syntax:concrete(Text)], - %% expand further if possible - expand(merl:qquote(Line, "merl:subst(_@tree, _@env)", - [{tree, eval_call(Line, quote, As, T)}, - {env, Env}])); + case eval_call(Line, quote, As, failed) of + failed -> + T; + T1 -> + %% expand further if possible + expand(merl:qquote(Line, "merl:subst(_@tree, _@env)", + [{tree, T1}, + {env, Env}])) + end; false -> T end; diff --git a/lib/syntax_tools/test/merl_SUITE.erl b/lib/syntax_tools/test/merl_SUITE.erl index 945972d405..52bbd9b3b8 100644 --- a/lib/syntax_tools/test/merl_SUITE.erl +++ b/lib/syntax_tools/test/merl_SUITE.erl @@ -29,12 +29,14 @@ init_per_group/2,end_per_group/2]). %% Test cases --export([merl_smoke_test/1]). +-export([merl_smoke_test/1, + transform_parse_error_test/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [merl_smoke_test]. + [merl_smoke_test, + transform_parse_error_test]. groups() -> []. @@ -84,6 +86,21 @@ merl_smoke_test(Config) when is_list(Config) -> end)), ok. +transform_parse_error_test(_Config) -> + ?assertEqual("merl:quote(\"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(\"{\")")], []))), + ?assertEqual("merl:quote(2, \"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(2, \"{\")")], []))), + ?assertEqual("merl:qquote(\"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(\"{\", [{var, V}])")], []))), + ?assertEqual("merl:qquote(2, \"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(2, \"{\", [{var, V}])")], []))), + ok. + %% utilities f(Ts) when is_list(Ts) -> diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl index b935d42bb7..43c17e9f1f 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl @@ -27,14 +27,14 @@ %% Test cases -export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1, t_abstract_type/1,t_erl_parse_type/1,t_epp_dodger/1, - t_comment_scan/1,t_igor/1]). + t_comment_scan/1,t_igor/1,t_erl_tidy/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [app_test,appup_test,smoke_test,revert,revert_map, t_abstract_type,t_erl_parse_type,t_epp_dodger, - t_comment_scan,t_igor]. + t_comment_scan,t_igor,t_erl_tidy]. groups() -> []. @@ -237,6 +237,12 @@ t_igor(Config) when is_list(Config) -> ok. +t_erl_tidy(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + File = filename:join(DataDir,"erl_tidy_tilde.erl"), + ok = erl_tidy:file(File, [{stdout, true}]), + ok. + test_comment_scan([],_) -> ok; test_comment_scan([File|Files],DataDir) -> Filename = filename:join(DataDir,File), diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl new file mode 100644 index 0000000000..888264bad6 --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl @@ -0,0 +1,13 @@ +%% +%% File: erl_tidy_tilde.erl +%% Author: Mark Bucciarelli +%% Created: 2016-06-05 +%% + +-module(erl_tidy_tilde). + +-export([start/0]). + +start() -> + io:put_chars("tilde characters ('~')in source were " + "breaking erl_tidy\n"). diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml index 00cf5196b4..7fef74813b 100644 --- a/lib/tools/doc/src/erlang_mode.xml +++ b/lib/tools/doc/src/erlang_mode.xml @@ -252,7 +252,14 @@ behavior</item> <item>gen_event - skeleton for the OTP gen_event behavior</item> <item>gen_fsm - skeleton for the OTP gen_fsm behavior</item> - <item>gen_statem - skeleton for the OTP gen_statem behavior</item> + <item> + gen_statem (StateName/3) - skeleton for the OTP gen_statem behavior + using state name functions + </item> + <item> + gen_statem (handle_event/4) - skeleton for the OTP gen_statem behavior + using one state function + </item> <item>Library module - skeleton for a module that does not implement a process.</item> <item>Corba callback - skeleton for a Corba callback module.</item> diff --git a/lib/tools/doc/src/xref_chapter.xml b/lib/tools/doc/src/xref_chapter.xml index 8b14e03064..872793bdcb 100644 --- a/lib/tools/doc/src/xref_chapter.xml +++ b/lib/tools/doc/src/xref_chapter.xml @@ -234,7 +234,7 @@ operand of the intersection operator <c>*</c> is implicitly converted to the more special type of the second operand.</item> <tag><c>xref:q(s, "(Mod) tools").</c></tag> - <item>All modules of the <c>tools</c> application.</item> + <item>All modules of the Tools application.</item> <tag><c>xref:q(s, '"xref_.*" : Mod').</c></tag> <item>All modules with a name beginning with <c>xref_</c>.</item> <tag><c>xref:q(s, "# E | X ").</c></tag> @@ -252,9 +252,9 @@ <tag><c>xref:q(s, "XC * (ME - strict ME)").</c></tag> <item>External calls within some module.</item> <tag><c>xref:q(s, "E ||| kernel").</c></tag> - <item>All calls within the <c>kernel</c> application. </item> + <item>All calls within the Kernel application. </item> <tag><c>xref:q(s, "closure E | kernel || kernel").</c></tag> - <item>All direct and indirect calls within the <c>kernel</c> + <item>All direct and indirect calls within the Kernel application. Both the calling and the used functions of indirect calls are defined in modules of the kernel application, but it is possible that some functions outside diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index ce26c83295..0284c9d686 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -56,8 +56,10 @@ erlang-skel-gen-event erlang-skel-header) ("gen_fsm" "gen-fsm" erlang-skel-gen-fsm erlang-skel-header) - ("gen_statem" "gen-statem" - erlang-skel-gen-statem erlang-skel-header) + ("gen_statem (StateName/3)" "gen-statem-StateName" + erlang-skel-gen-statem-StateName erlang-skel-header) + ("gen_statem (handle_event/4)" "gen-statem-handle-event" + erlang-skel-gen-statem-handle-event erlang-skel-header) ("wx_object" "wx-object" erlang-skel-wx-object erlang-skel-header) ("Library module" "gen-lib" @@ -497,6 +499,7 @@ Please see the function `tempo-define-template'.") "%% {stop, Reason}" n (erlang-skel-separator-end 2) "init([]) ->" n> + "process_flag(trap_exit, true)," n> "{ok, #state{}}." n n (erlang-skel-separator-start 2) @@ -740,6 +743,7 @@ Please see the function `tempo-define-template'.") "%% {stop, StopReason}" n (erlang-skel-separator-end 2) "init([]) ->" n> + "process_flag(trap_exit, true)," n> "{ok, state_name, #state{}}." n n (erlang-skel-separator-start 2) @@ -860,7 +864,7 @@ Please see the function `tempo-define-template'.") "*The template of a gen_fsm. Please see the function `tempo-define-template'.") -(defvar erlang-skel-gen-statem +(defvar erlang-skel-gen-statem-StateName '((erlang-skel-include erlang-skel-large-header) "-behaviour(gen_statem)." n n @@ -868,9 +872,8 @@ Please see the function `tempo-define-template'.") "-export([start_link/0])." n n "%% gen_statem callbacks" n - "-export([init/1, terminate/3, code_change/4])." n + "-export([callback_mode/0, init/1, terminate/3, code_change/4])." n "-export([state_name/3])." n - "-export([handle_event/4])." n n "-define(SERVER, ?MODULE)." n n @@ -899,48 +902,151 @@ Please see the function `tempo-define-template'.") (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n + "%% Define the callback_mode() for this callback module." n + (erlang-skel-separator-end 2) + "-spec callback_mode() -> gen_statem:callback_mode()." n + "callback_mode() -> state_functions." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n "%% Whenever a gen_statem is started using gen_statem:start/[3,4] or" n "%% gen_statem:start_link/[3,4], this function is called by the new" n "%% process to initialize." n (erlang-skel-separator-end 2) - "-spec init(Args :: term()) -> " n> - "{gen_statem:callback_mode()," n> - "State :: term(), Data :: term()} |" n> - "{gen_statem:callback_mode()," n> - "State :: term(), Data :: term()," n> + "-spec init(Args :: term()) ->" n> + "{ok, State :: term(), Data :: term()} |" n> + "{ok, State :: term(), Data :: term()," n> "[gen_statem:action()] | gen_statem:action()} |" n> "ignore |" n> "{stop, Reason :: term()}." n "init([]) ->" n> - "{state_functions, state_name, #data{}}." n + "process_flag(trap_exit, true)," n> + "{ok, state_name, #data{}}." n n (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n - "%% If the gen_statem runs with CallbackMode =:= state_functions" n - "%% there should be one instance of this function for each possible" n - "%% state name. Whenever a gen_statem receives an event," n - "%% the instance of this function with the same name" n - "%% as the current state name StateName is called to" n - "%% handle the event." n + "%% There should be one function like this for each state name." n + "%% Whenever a gen_statem receives an event, the function " n + "%% with the name of the current state (StateName) " n + "%% is called to handle the event." n + "%%" n + "%% NOTE: If there is an exported function handle_event/4, it is called" n + "%% instead of StateName/3 functions like this!" n (erlang-skel-separator-end 2) "-spec state_name(" n> "gen_statem:event_type(), Msg :: term()," n> "Data :: term()) ->" n> - "gen_statem:state_function_result(). " n + "gen_statem:state_function_result()." n "state_name({call,Caller}, _Msg, Data) ->" n> "{next_state, state_name, Data, [{reply,Caller,ok}]}." n n (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n - "%% If the gen_statem runs with CallbackMode =:= handle_event_function" n - "%% this function is called for every event a gen_statem receives." n + "%% This function is called by a gen_statem when it is about to" n + "%% terminate. It should be the opposite of Module:init/1 and do any" n + "%% necessary cleaning up. When it returns, the gen_statem terminates with" n + "%% Reason. The return value is ignored." n + (erlang-skel-separator-end 2) + "-spec terminate(Reason :: term(), State :: term(), Data :: term()) ->" n> + "any()." n + "terminate(_Reason, _State, _Data) ->" n> + "void." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Convert process state when code is changed" n + (erlang-skel-separator-end 2) + "-spec code_change(" n> + "OldVsn :: term() | {down,term()}," n> + "State :: term(), Data :: term(), Extra :: term()) ->" n> + "{ok, NewState :: term(), NewData :: term()} |" n> + "(Reason :: term())." n + "code_change(_OldVsn, State, Data, _Extra) ->" n> + "{ok, State, Data}." n + n + (erlang-skel-double-separator-start 3) + "%%% Internal functions" n + (erlang-skel-double-separator-end 3) + ) + "*The template of a gen_statem (StateName/3). +Please see the function `tempo-define-template'.") + +(defvar erlang-skel-gen-statem-handle-event + '((erlang-skel-include erlang-skel-large-header) + "-behaviour(gen_statem)." n n + + "%% API" n + "-export([start_link/0])." n + n + "%% gen_statem callbacks" n + "-export([callback_mode/0, init/1, terminate/3, code_change/4])." n + "-export([handle_event/4])." n + n + "-define(SERVER, ?MODULE)." n + n + "-record(data, {})." n + n + (erlang-skel-double-separator-start 3) + "%%% API" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @doc" n + "%% Creates a gen_statem process which calls Module:init/1 to" n + "%% initialize. To ensure a synchronized start-up procedure, this" n + "%% function does not return until Module:init/1 has returned." n + "%%" n + (erlang-skel-separator-end 2) + "-spec start_link() ->" n> + "{ok, Pid :: pid()} |" n> + "ignore |" n> + "{error, Error :: term()}." n + "start_link() ->" n> + "gen_statem:start_link({local, ?SERVER}, ?MODULE, [], [])." n + n + (erlang-skel-double-separator-start 3) + "%%% gen_statem callbacks" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Define the callback_mode() for this callback module." n + (erlang-skel-separator-end 2) + "-spec callback_mode() -> gen_statem:callback_mode()." n + "callback_mode() -> handle_event_function." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Whenever a gen_statem is started using gen_statem:start/[3,4] or" n + "%% gen_statem:start_link/[3,4], this function is called by the new" n + "%% process to initialize." n + (erlang-skel-separator-end 2) + "-spec init(Args :: term()) ->" n> + "{ok, State :: term(), Data :: term()} |" n> + "{ok, State :: term(), Data :: term()," n> + "[gen_statem:action()] | gen_statem:action()} |" n> + "ignore |" n> + "{stop, Reason :: term()}." n + "init([]) ->" n> + "process_flag(trap_exit, true)," n> + "{ok, state_name, #data{}}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% This function is called for every event a gen_statem receives." n + "%%" n + "%% NOTE: If there is no exported function handle_event/4," n + "%% StateName/3 functions are called instead!" n (erlang-skel-separator-end 2) "-spec handle_event(" n> "gen_statem:event_type(), Msg :: term()," n> "State :: term(), Data :: term()) ->" n> - "gen_statem:handle_event_result(). " n + "gen_statem:handle_event_result()." n "handle_event({call,From}, _Msg, State, Data) ->" n> "{next_state, State, Data, [{reply,From,ok}]}." n n @@ -965,7 +1071,8 @@ Please see the function `tempo-define-template'.") "-spec code_change(" n> "OldVsn :: term() | {down,term()}," n> "State :: term(), Data :: term(), Extra :: term()) ->" n> - "{ok, NewState :: term(), NewData :: term()}." n + "{ok, NewState :: term(), NewData :: term()} |" n> + "(Reason :: term())." n "code_change(_OldVsn, State, Data, _Extra) ->" n> "{ok, State, Data}." n n @@ -973,7 +1080,7 @@ Please see the function `tempo-define-template'.") "%%% Internal functions" n (erlang-skel-double-separator-end 3) ) - "*The template of a gen_statem. + "*The template of a gen_statem (handle_event/4). Please see the function `tempo-define-template'.") (defvar erlang-skel-wx-object diff --git a/lib/tools/emacs/erlang-test.el b/lib/tools/emacs/erlang-test.el index 9a146632c5..ba6190d194 100644 --- a/lib/tools/emacs/erlang-test.el +++ b/lib/tools/emacs/erlang-test.el @@ -52,28 +52,42 @@ concatenated to form an erlang file to test on.") (ert-deftest erlang-test-tags () (let* ((dir (make-temp-file "erlang-test" t)) - (erlang-file (expand-file-name "erlang_test.erl" dir)) - (tags-file (expand-file-name "TAGS" dir)) - tags-file-name tags-table-list erlang-buffer) - (unwind-protect - (progn - (erlang-test-create-erlang-file erlang-file) - (erlang-test-compile-tags erlang-file tags-file) - (setq erlang-buffer (find-file-noselect erlang-file)) - (with-current-buffer erlang-buffer - (setq-local tags-file-name tags-file)) - ;; Setting global tags-file-name is a workaround for - ;; GNU Emacs bug#23164. - (setq tags-file-name tags-file) - (erlang-test-completion-table) - (erlang-test-xref-find-definitions erlang-file erlang-buffer)) - (when (buffer-live-p erlang-buffer) - (kill-buffer erlang-buffer)) - (let ((tags-buffer (find-buffer-visiting tags-file))) - (when (buffer-live-p tags-buffer) - (kill-buffer tags-buffer))) - (when (file-exists-p dir) - (delete-directory dir t))))) + (erlang-file (expand-file-name "erlang_test.erl" dir)) + (tags-file (expand-file-name "TAGS" dir)) + (old-tags-file-name (default-value 'tags-file-name)) + (old-tags-table-list (default-value 'tags-table-list)) + tags-file-name + tags-table-list + tags-table-set-list + erlang-buffer + erlang-mode-hook + prog-mode-hook + erlang-shell-mode-hook + tags-add-tables) + (unwind-protect + (progn + (setq-default tags-file-name nil) + (setq-default tags-table-list nil) + (erlang-test-create-erlang-file erlang-file) + (erlang-test-compile-tags erlang-file tags-file) + (setq erlang-buffer (find-file-noselect erlang-file)) + (with-current-buffer erlang-buffer + (setq-local tags-file-name tags-file)) + ;; Setting global tags-file-name is a workaround for + ;; GNU Emacs bug#23164. + (setq tags-file-name tags-file) + (erlang-test-complete-at-point tags-file) + (erlang-test-completion-table) + (erlang-test-xref-find-definitions erlang-file erlang-buffer)) + (when (buffer-live-p erlang-buffer) + (kill-buffer erlang-buffer)) + (let ((tags-buffer (find-buffer-visiting tags-file))) + (when (buffer-live-p tags-buffer) + (kill-buffer tags-buffer))) + (when (file-exists-p dir) + (delete-directory dir t)) + (setq-default tags-file-name old-tags-file-name) + (setq-default tags-table-list old-tags-table-list)))) (defun erlang-test-create-erlang-file (erlang-file) (with-temp-file erlang-file @@ -116,6 +130,54 @@ concatenated to form an erlang file to test on.") (should (eq expected-line (line-number-at-pos))) (should (= (point-at-bol) (point)))) +(defun erlang-test-complete-at-point (tags-file) + (with-temp-buffer + (erlang-mode) + (setq-local tags-file-name tags-file) + (insert "\nerlang_test:fun") + (erlang-complete-tag) + (should (looking-back "erlang_test:function")) + (insert "\nfun") + (erlang-complete-tag) + (should (looking-back "function")) + (insert "\nerlang_") + (erlang-complete-tag) + (should (looking-back "erlang_test:")))) + + +(ert-deftest erlang-test-compile-options () + (erlang-test-format-opt t + "t") + (erlang-test-format-opt nil + "nil") + (erlang-test-format-opt (cons 1 2) + "{1, 2}") + (erlang-test-format-opt (list 1) + "[1]") + (erlang-test-format-opt (list 1 2) + "[1, 2]") + (erlang-test-format-opt (list 1 2 3) + "[1, 2, 3]") + (erlang-test-format-opt 'symbol + "symbol") + (erlang-test-format-opt "string" + "\"string\"") + (erlang-test-format-opt [] + "{}") + (erlang-test-format-opt [1] + "{1}") + (erlang-test-format-opt [1 2] + "{1, 2}") + (erlang-test-format-opt [1 2 (3 [4 5 6] 7)] + "{1, 2, [3, {4, 5, 6}, 7]}")) + +(defun erlang-test-format-opt (elisp &optional expected-erlang) + (let ((erlang (inferior-erlang-format-opt elisp))) + (message "%s -> %s" elisp erlang) + (when expected-erlang + (should (equal erlang expected-erlang))) + erlang)) + (provide 'erlang-test) diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index a2062180f3..73c6b8d768 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -1436,8 +1436,6 @@ Other commands: (erlang-skel-init) (when (fboundp 'tempo-use-tag-list) (tempo-use-tag-list 'erlang-tempo-tags)) - (when (boundp 'xref-backend-functions) - (add-hook 'xref-backend-functions #'erlang-etags--xref-backend nil t)) (run-hooks 'erlang-mode-hook) (if (zerop (buffer-size)) (run-hooks 'erlang-new-file-hook))) @@ -1548,9 +1546,7 @@ Other commands: (set (make-local-variable 'outline-regexp) "[[:lower:]0-9_]+ *(.*) *-> *$") (set (make-local-variable 'outline-level) (lambda () 1)) (set (make-local-variable 'add-log-current-defun-function) - 'erlang-current-defun) - (set (make-local-variable 'find-tag-default-function) - 'erlang-find-tag-for-completion)) + 'erlang-current-defun)) (defun erlang-font-lock-init () "Initialize Font Lock for Erlang mode." @@ -3233,18 +3229,16 @@ With argument, do this that many times." (interactive "p") (or arg (setq arg 1)) (while (and (looking-at "[ \t]*[%\n]") - (zerop (forward-line 1)))) + (zerop (forward-line 1)))) ;; Move to the next clause. (erlang-beginning-of-clause (- arg)) (beginning-of-line);; Just to be sure... (let ((continue t)) (while (and (not (bobp)) continue) (forward-line -1) - (skip-chars-forward " \t") - (if (looking-at "[%\n]") - nil - (end-of-line) - (setq continue nil))))) + (unless (looking-at "[ \t]*[%\n]") + (end-of-line) + (setq continue nil))))) (defun erlang-mark-clause () "Put mark at end of clause, point at beginning." @@ -4352,11 +4346,6 @@ as on the old form `tag'. In the completion list, `module:tag' and `module:' shows up. -Call this function from an appropriate init file, or add it to -Erlang mode hook with the commands: - (add-hook 'erlang-mode-hook 'erlang-tags-init) - (add-hook 'erlang-shell-mode-hook 'erlang-tags-init) - This function only works under Emacs 18 and Emacs 19. Currently, It is not implemented under XEmacs. (Hint: The Emacs 19 etags module works under XEmacs.)" @@ -4367,11 +4356,16 @@ works under XEmacs.)" (setq erlang-tags-installed t)) (t (require 'etags) - ;; Test on a function available in the Emacs 19 version - ;; of tags but not in the XEmacs version. - (when (fboundp 'find-tag-noselect) - (erlang-tags-define-keys (current-local-map)) - (setq erlang-tags-installed t))))) + (set (make-local-variable 'find-tag-default-function) + 'erlang-find-tag-for-completion) + (if (>= emacs-major-version 25) + (add-hook 'xref-backend-functions + #'erlang-etags--xref-backend nil t) + ;; Test on a function available in the Emacs 19 version + ;; of tags but not in the XEmacs version. + (when (fboundp 'find-tag-noselect) + (erlang-tags-define-keys (current-local-map)) + (setq erlang-tags-installed t)))))) @@ -4775,8 +4769,6 @@ for a tag on the form `module:tag'." ;;; completion-table' containing all normal tags plus tags on the form ;;; `module:tag' and `module:'. -;; PENDING - Should probably make use of the -;; `completion-at-point-functions' hook instead of this advice. (when (and (locate-library "etags") (require 'etags) (fboundp 'etags-tags-completion-table) @@ -4784,11 +4776,7 @@ for a tag on the form `module:tag'." (if (fboundp 'advice-add) ;; Emacs 24.4+ (advice-add 'etags-tags-completion-table :around - (lambda (oldfun) - (if erlang-replace-etags-tags-completion-table - (erlang-etags-tags-completion-table) - (funcall oldfun))) - (list :name 'erlang-replace-tags-table)) + #'erlang-etags-tags-completion-table-advice) ;; Emacs 23.1-24.3 (defadvice etags-tags-completion-table (around erlang-replace-tags-table @@ -4797,6 +4785,11 @@ for a tag on the form `module:tag'." (setq ad-return-value (erlang-etags-tags-completion-table)) ad-do-it)))) +(defun erlang-etags-tags-completion-table-advice (oldfun) + (if erlang-replace-etags-tags-completion-table + (erlang-etags-tags-completion-table) + (funcall oldfun))) + (defun erlang-complete-tag () "Perform tags completion on the text around point. Completes to the set of names listed in the current tags table. @@ -4807,8 +4800,7 @@ about Erlang modules." (condition-case nil (require 'etags) (error nil)) - (cond ((and erlang-tags-installed - (fboundp 'etags-tags-completion-table) + (cond ((and (fboundp 'etags-tags-completion-table) (fboundp 'tags-lazy-completion-table)) ; Emacs 23.1+ (let ((erlang-replace-etags-tags-completion-table t)) (complete-tag))) @@ -5213,19 +5205,10 @@ The following special commands are available: (setq comint-input-ignoredups t) (setq comint-scroll-show-maximum-output t) (setq comint-scroll-to-bottom-on-output t) - ;; In Emacs 19.30, `add-hook' has got a `local' flag, use it. If - ;; the call fails, just call the normal `add-hook'. - (condition-case nil - (progn - (add-hook 'comint-output-filter-functions - 'inferior-erlang-strip-delete nil t) - (add-hook 'comint-output-filter-functions - 'inferior-erlang-strip-ctrl-m nil t)) - (error - (funcall (symbol-function 'make-local-hook) - 'comint-output-filter-functions) ; obsolete as of Emacs 21.1 - (add-hook 'comint-output-filter-functions 'inferior-erlang-strip-delete) - (add-hook 'comint-output-filter-functions 'inferior-erlang-strip-ctrl-m))) + (add-hook 'comint-output-filter-functions + 'inferior-erlang-strip-delete nil t) + (add-hook 'comint-output-filter-functions + 'inferior-erlang-strip-ctrl-m nil t) ;; Some older versions of comint don't have an input ring. (if (fboundp 'comint-read-input-ring) (progn @@ -5251,6 +5234,7 @@ The following special commands are available: (define-key map [menu-bar compilation] (cons "Errors" compilation-menu-map))) map))))) + (erlang-tags-init) (run-hooks 'erlang-shell-mode-hook)) @@ -5730,31 +5714,29 @@ unless the optional NO-DISPLAY is non-nil." (defun inferior-erlang-format-comma-opts (opts) (if (null opts) "" - (concat ", " (inferior-erlang-format-opts opts)))) - -(defun inferior-erlang-format-opts (opts) - (concat "[" (inferior-erlang-string-join (mapcar 'inferior-erlang-format-opt - opts) - ", ") - "]")) + (concat ", " (inferior-erlang-format-opt opts)))) (defun inferior-erlang-format-opt (opt) (cond ((stringp opt) (concat "\"" opt "\"")) - ((atom opt) (format "%s" opt)) - ((consp opt) (concat "{" (inferior-erlang-string-join - (mapcar 'inferior-erlang-format-opt - (list (car opt) (cdr opt))) - ", ") - "}")) - (t (error (format "Unexpected opt %s" opt))))) - -(defun inferior-erlang-string-join (strs sep) - (let ((result (or (car strs) ""))) - (setq strs (cdr strs)) - (while strs - (setq result (concat result sep (car strs))) - (setq strs (cdr strs))) - result)) + ((vectorp opt) (inferior-erlang-tuple (append opt nil))) + ((atom opt) (format "%s" opt)) + ((consp opt) (if (listp (cdr opt)) + (inferior-erlang-list opt) + (inferior-erlang-tuple (list (car opt) (cdr opt))))) + (t (error "Unexpected erlang compile option %s" opt)))) + +(defun inferior-erlang-tuple (opts) + (concat "{" (mapconcat 'inferior-erlang-format-opt + opts + ", ") + "}")) + +(defun inferior-erlang-list (opts) + (concat "[" (mapconcat 'inferior-erlang-format-opt + opts + ", ") + "]")) + (defun erlang-local-buffer-file-name () ;; When editing a file remotely via tramp, diff --git a/lib/tools/src/fprof.erl b/lib/tools/src/fprof.erl index 8db23dd151..1291a3e5ec 100644 --- a/lib/tools/src/fprof.erl +++ b/lib/tools/src/fprof.erl @@ -1636,6 +1636,11 @@ trace_handler({trace_ts, Pid, gc_major_start, _Func, TS} = Trace, Table, _, Dump dump_stack(Dump, get(Pid), Trace), trace_gc_start(Table, Pid, TS), TS; + +trace_handler({trace_ts, Pid, gc_start, _Func, TS} = Trace, Table, _, Dump) -> + dump_stack(Dump, get(Pid), Trace), + trace_gc_start(Table, Pid, TS), + TS; %% %% gc_end @@ -1648,6 +1653,12 @@ trace_handler({trace_ts, Pid, gc_major_end, _Func, TS} = Trace, Table, _, Dump) dump_stack(Dump, get(Pid), Trace), trace_gc_end(Table, Pid, TS), TS; + +trace_handler({trace_ts, Pid, gc_end, _Func, TS} = Trace, Table, _, Dump) -> + dump_stack(Dump, get(Pid), Trace), + trace_gc_end(Table, Pid, TS), + TS; + %% %% link trace_handler({trace_ts, Pid, link, _OtherPid, TS} = Trace, diff --git a/lib/tools/src/make.erl b/lib/tools/src/make.erl index 26378f28a0..37e67cbe34 100644 --- a/lib/tools/src/make.erl +++ b/lib/tools/src/make.erl @@ -317,5 +317,7 @@ check_includes2(Epp, File, ObjMTime) -> epp:close(Epp), false; {error, _Error} -> + check_includes2(Epp, File, ObjMTime); + {warning, _Warning} -> check_includes2(Epp, File, ObjMTime) end. diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index a00969eabe..18166cceb0 100644 --- a/lib/tools/src/tools.app.src +++ b/lib/tools/src/tools.app.src @@ -40,7 +40,7 @@ {env, [{file_util_search_methods,[{"", ""}, {"ebin", "esrc"}, {"ebin", "src"}]} ] }, - {runtime_dependencies, ["stdlib-2.5","runtime_tools-1.8.14", + {runtime_dependencies, ["stdlib-3.1","runtime_tools-1.8.14", "kernel-3.0","inets-5.10","erts-7.0", "compiler-5.0"]} ] diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index b4c9264b30..90e113c178 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -1001,41 +1001,40 @@ otp_6115(Config) when is_list(Config) -> %% Cover compile f1, but not f2 {ok, f1} = cover:compile(f1), + %% This test used to ensure that a process containing a + %% fun refering to cover compiled code was killed. + %% check_process_code may however ignore funs as of ERTS + %% version 8.1. The test has therefore been rewritten to + %% check that a process with a direct reference (in this + %% case a return address) to the code is killed. + %% %% If f1 is cover compiled, a process P is started with a - %% reference to the fun created in start_fail/0, and cover:stop() is - %% called, then P should be killed. - %% This is because (the fun held by P) references the cover + %% direct reference to the f1, and cover:stop() is called, + %% then P should be killed. + %% This is because of the return address to the cover %% compiled code which should be *unloaded* when cover:stop() is %% called -- running cover compiled code when there is no cover %% server and thus no ets tables to bump counters in, makes no %% sense. - Pid1 = f1:start_a(), - Pid2 = f1:start_b(), + Pid = spawn(fun () -> f1:non_tail_call_f2_wait() end), %% Now stop cover cover:stop(), %% Ensure that f1 is loaded (and not cover compiled), and that - %% both Pid1 and Pid2 are dead. + %% both Pid is dead. case code:which(f1) of Beam when is_list(Beam) -> ok; Other -> ct:fail({"f1 is not reloaded", Other}) end, - case process_info(Pid1) of + case process_info(Pid) of undefined -> ok; - _PI1 -> - RefToOldP1 = erlang:check_process_code(Pid1, f1), - ct:fail({"Pid1 still alive", RefToOldP1}) - end, - case process_info(Pid2) of - undefined -> - ok; - _PI2 -> - RefToOldP2 = erlang:check_process_code(Pid1, f2), - ct:fail({"Pid2 still alive", RefToOldP2}) + _PI -> + RefToOldP = erlang:check_process_code(Pid, f1), + ct:fail({"Pid still alive", RefToOldP}) end, file:set_cwd(CWD), diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl index 5399b33f19..fc4a62e70e 100644 --- a/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl +++ b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl @@ -1,13 +1,6 @@ -module(f1). --export([start_a/0, start_b/0]). +-export([non_tail_call_f2_wait/0]). -start_a() -> - f2:start(fun() -> - ok - end). - -start_b() -> - f2:start(fun fun1/0). - -fun1() -> - ok. +non_tail_call_f2_wait() -> + f2:wait(), + im_back. diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl index 72a6a64c4d..4bc88035c7 100644 --- a/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl +++ b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl @@ -1,13 +1,5 @@ -module(f2). --export([start/1]). +-export([wait/0]). -start(Fun) -> - spawn(fun() -> - wait(Fun) - end). - -wait(Fun) -> - receive - go -> - Fun() - end. +wait() -> + receive after infinity -> ok end. diff --git a/lib/tools/test/make_SUITE_data/test1.erl b/lib/tools/test/make_SUITE_data/test1.erl index f4a133008e..9e21bdc767 100644 --- a/lib/tools/test/make_SUITE_data/test1.erl +++ b/lib/tools/test/make_SUITE_data/test1.erl @@ -3,6 +3,8 @@ -vsn('$Revision: /main/release/2 $'). -compile(export_all). +-warning("a warning"). + f1() -> true. diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index e6287b0430..e066dbf5e9 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 2.8.5 +TOOLS_VSN = 2.8.6 diff --git a/lib/wx/api_gen/wx_extra/wxListCtrl.erl b/lib/wx/api_gen/wx_extra/wxListCtrl.erl index acdb69fdeb..355a4cdfd1 100644 --- a/lib/wx/api_gen/wx_extra/wxListCtrl.erl +++ b/lib/wx/api_gen/wx_extra/wxListCtrl.erl @@ -43,33 +43,36 @@ SortItems>> <<EXPORT:wxListCtrl new/0, new/1, new/2 wxListCtrl:EXPORT>> <<wxListCtrl_new_0 -%% @spec () -> wxListCtrl() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new() -> wxListCtrl(). new() -> wxe_util:construct(~s, <<>>). wxListCtrl_new_0>> <<wxListCtrl_new_2 -%% @spec (Parent::wxWindow:wxWindow()) -> wxListCtrl() -%% @equiv new(Parent, []) +-spec new(Parent) -> wxListCtrl() when + Parent::wxWindow:wxWindow(). new(Parent) when is_record(Parent, wx_ref) -> new(Parent, []). -%% @spec (Parent::wxWindow:wxWindow(), [Option]) -> wxListCtrl() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} +%% @doc Creates a listctrl with optional callback functions: %% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() +%% OnGetItemText = (This, Item, Column) -> unicode:charlist() +%% OnGetItemAttr = (This, Item) -> wxListItemAttr:wxListItemAttr() %% OnGetItemColumnImage = (This, Item, Column) -> integer() -%% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +%% +%% See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new(Parent, [Option]) -> wxListCtrl() when + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. new(#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options)-> @@ -101,26 +104,27 @@ wxListCtrl_new_2>> <<EXPORT:Create create/2, create/3 Create:EXPORT>> <<Create -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow()) -> bool() %% @equiv create(This,Parent, []) +-spec create(This, Parent) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(). create(This,Parent) when is_record(This, wx_ref),is_record(Parent, wx_ref) -> create(This,Parent, []). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow(), [Option]) -> bool() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} -%% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() -%% OnGetItemColumnImage = (This, Item, Column) -> integer() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlcreate">external documentation</a>. +-spec create(This, Parent, [Option]) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. + create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options) -> ?CLASS(ThisT,wxListCtrl), diff --git a/lib/wx/api_gen/wx_extra/wxXmlResource.erl b/lib/wx/api_gen/wx_extra/wxXmlResource.erl index 7700e2333e..b29ffba7c6 100644 --- a/lib/wx/api_gen/wx_extra/wxXmlResource.erl +++ b/lib/wx/api_gen/wx_extra/wxXmlResource.erl @@ -21,8 +21,6 @@ <<EXPORT:xrcctrl xrcctrl/3 xrcctrl:EXPORT>> <<xrcctrl -%% @spec (Window::wxWindow:wxWindow(),Name::string(), Type::atom()) -> wx:wxObject() - %% @doc Looks up a control with Name in a window created with XML %% resources. You can use it to set/get values from controls. %% The object is type casted to <b>Type</b>. @@ -32,6 +30,10 @@ %% true = wxXmlResource:loadDialog(Xrc, Dlg, Frame, "controls_dialog"), <br /> %% LCtrl = xrcctrl(Dlg, "controls_listctrl", wxListCtrl), <br /> %% wxListCtrl:insertColumn(LCtrl, 0, "Name", [{width, 200}]), <br /> +-spec xrcctrl(Window, Name, Type) -> wx:wx_object() when + Window::wxWindow:wxWindow(), + Name::string(), + Type::atom(). xrcctrl(Window = #wx_ref{}, Name, Type) when is_list(Name), is_atom(Type) -> %% Func Id ~s diff --git a/lib/wx/c_src/wxe_main.cpp b/lib/wx/c_src/wxe_main.cpp index 6fcde42eb5..c7565e33bd 100644 --- a/lib/wx/c_src/wxe_main.cpp +++ b/lib/wx/c_src/wxe_main.cpp @@ -67,6 +67,7 @@ int load_native_gui() int start_native_gui(wxe_data *sd) { int res; + ErlDrvThreadOpts *opts = NULL; wxe_status_m = erl_drv_mutex_create((char *) "wxe_status_m"); wxe_status_c = erl_drv_cond_create((char *)"wxe_status_c"); @@ -78,8 +79,11 @@ int start_native_gui(wxe_data *sd) res = erl_drv_steal_main_thread((char *)"wxwidgets", &wxe_thread,wxe_main_loop,(void *) sd->pdl,NULL); #else + opts = erl_drv_thread_opts_create((char *)"wx thread"); + opts->suggested_stack_size = 8192; res = erl_drv_thread_create((char *)"wxwidgets", - &wxe_thread,wxe_main_loop,(void *) sd->pdl,NULL); + &wxe_thread,wxe_main_loop,(void *) sd->pdl,opts); + erl_drv_thread_opts_destroy(opts); #endif if(res == 0) { erl_drv_mutex_lock(wxe_status_m); diff --git a/lib/wx/doc/specs/.gitignore b/lib/wx/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/wx/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/wx/doc/src/Makefile b/lib/wx/doc/src/Makefile index cae2f9fe4e..23890f47f0 100644 --- a/lib/wx/doc/src/Makefile +++ b/lib/wx/doc/src/Makefile @@ -63,10 +63,15 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) + +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- XML_FLAGS += +SPECS_FLAGS = -I../../include -I../../src DVIPS_FLAGS += # ---------------------------------------------------- @@ -111,6 +116,7 @@ clean clean_docs: rm -rf $(HTMLDIR)/* rm -f $(MAN3DIR)/* rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECDIR)/* rm -f errs core *~ ../html/edoc-info rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html diff --git a/lib/wx/doc/src/specs.xml b/lib/wx/doc/src/specs.xml new file mode 100644 index 0000000000..2e19baafc4 --- /dev/null +++ b/lib/wx/doc/src/specs.xml @@ -0,0 +1,232 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_wx.xml"/> + <xi:include href="../specs/specs_wx_object.xml"/> + <xi:include href="../specs/specs_wxAcceleratorEntry.xml"/> + <xi:include href="../specs/specs_wxAcceleratorTable.xml"/> + <xi:include href="../specs/specs_wxActivateEvent.xml"/> + <xi:include href="../specs/specs_wxArtProvider.xml"/> + <xi:include href="../specs/specs_wxAuiDockArt.xml"/> + <xi:include href="../specs/specs_wxAuiManager.xml"/> + <xi:include href="../specs/specs_wxAuiManagerEvent.xml"/> + <xi:include href="../specs/specs_wxAuiNotebook.xml"/> + <xi:include href="../specs/specs_wxAuiNotebookEvent.xml"/> + <xi:include href="../specs/specs_wxAuiPaneInfo.xml"/> + <xi:include href="../specs/specs_wxAuiSimpleTabArt.xml"/> + <xi:include href="../specs/specs_wxAuiTabArt.xml"/> + <xi:include href="../specs/specs_wxBitmapButton.xml"/> + <xi:include href="../specs/specs_wxBitmapDataObject.xml"/> + <xi:include href="../specs/specs_wxBitmap.xml"/> + <xi:include href="../specs/specs_wxBoxSizer.xml"/> + <xi:include href="../specs/specs_wxBrush.xml"/> + <xi:include href="../specs/specs_wxBufferedDC.xml"/> + <xi:include href="../specs/specs_wxBufferedPaintDC.xml"/> + <xi:include href="../specs/specs_wxButton.xml"/> + <xi:include href="../specs/specs_wxCalendarCtrl.xml"/> + <xi:include href="../specs/specs_wxCalendarDateAttr.xml"/> + <xi:include href="../specs/specs_wxCalendarEvent.xml"/> + <xi:include href="../specs/specs_wxCaret.xml"/> + <xi:include href="../specs/specs_wxCheckBox.xml"/> + <xi:include href="../specs/specs_wxCheckListBox.xml"/> + <xi:include href="../specs/specs_wxChildFocusEvent.xml"/> + <xi:include href="../specs/specs_wxChoicebook.xml"/> + <xi:include href="../specs/specs_wxChoice.xml"/> + <xi:include href="../specs/specs_wxClientDC.xml"/> + <xi:include href="../specs/specs_wxClipboard.xml"/> + <xi:include href="../specs/specs_wxClipboardTextEvent.xml"/> + <xi:include href="../specs/specs_wxCloseEvent.xml"/> + <xi:include href="../specs/specs_wxColourData.xml"/> + <xi:include href="../specs/specs_wxColourDialog.xml"/> + <xi:include href="../specs/specs_wxColourPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxColourPickerEvent.xml"/> + <xi:include href="../specs/specs_wxComboBox.xml"/> + <xi:include href="../specs/specs_wxCommandEvent.xml"/> + <xi:include href="../specs/specs_wxContextMenuEvent.xml"/> + <xi:include href="../specs/specs_wxControl.xml"/> + <xi:include href="../specs/specs_wxControlWithItems.xml"/> + <xi:include href="../specs/specs_wxCursor.xml"/> + <xi:include href="../specs/specs_wxDataObject.xml"/> + <xi:include href="../specs/specs_wxDateEvent.xml"/> + <xi:include href="../specs/specs_wxDatePickerCtrl.xml"/> + <xi:include href="../specs/specs_wxDC.xml"/> + <xi:include href="../specs/specs_wxDCOverlay.xml"/> + <xi:include href="../specs/specs_wxDialog.xml"/> + <xi:include href="../specs/specs_wxDirDialog.xml"/> + <xi:include href="../specs/specs_wxDirPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxDisplayChangedEvent.xml"/> + <xi:include href="../specs/specs_wxEraseEvent.xml"/> + <xi:include href="../specs/specs_wxEvent.xml"/> + <xi:include href="../specs/specs_wxEvtHandler.xml"/> + <xi:include href="../specs/specs_wxFileDataObject.xml"/> + <xi:include href="../specs/specs_wxFileDialog.xml"/> + <xi:include href="../specs/specs_wxFileDirPickerEvent.xml"/> + <xi:include href="../specs/specs_wxFilePickerCtrl.xml"/> + <xi:include href="../specs/specs_wxFindReplaceData.xml"/> + <xi:include href="../specs/specs_wxFindReplaceDialog.xml"/> + <xi:include href="../specs/specs_wxFlexGridSizer.xml"/> + <xi:include href="../specs/specs_wxFocusEvent.xml"/> + <xi:include href="../specs/specs_wxFontData.xml"/> + <xi:include href="../specs/specs_wxFontDialog.xml"/> + <xi:include href="../specs/specs_wxFont.xml"/> + <xi:include href="../specs/specs_wxFontPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxFontPickerEvent.xml"/> + <xi:include href="../specs/specs_wxFrame.xml"/> + <xi:include href="../specs/specs_wxGauge.xml"/> + <xi:include href="../specs/specs_wxGBSizerItem.xml"/> + <xi:include href="../specs/specs_wxGenericDirCtrl.xml"/> + <xi:include href="../specs/specs_wxGLCanvas.xml"/> + <xi:include href="../specs/specs_wxGraphicsBrush.xml"/> + <xi:include href="../specs/specs_wxGraphicsContext.xml"/> + <xi:include href="../specs/specs_wxGraphicsFont.xml"/> + <xi:include href="../specs/specs_wxGraphicsMatrix.xml"/> + <xi:include href="../specs/specs_wxGraphicsObject.xml"/> + <xi:include href="../specs/specs_wxGraphicsPath.xml"/> + <xi:include href="../specs/specs_wxGraphicsPen.xml"/> + <xi:include href="../specs/specs_wxGraphicsRenderer.xml"/> + <xi:include href="../specs/specs_wxGridBagSizer.xml"/> + <xi:include href="../specs/specs_wxGridCellAttr.xml"/> + <xi:include href="../specs/specs_wxGridCellBoolEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellBoolRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellChoiceEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellFloatEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellFloatRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellNumberEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellNumberRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellStringRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellTextEditor.xml"/> + <xi:include href="../specs/specs_wxGrid.xml"/> + <xi:include href="../specs/specs_wxGridEvent.xml"/> + <xi:include href="../specs/specs_wxGridSizer.xml"/> + <xi:include href="../specs/specs_wxHelpEvent.xml"/> + <xi:include href="../specs/specs_wxHtmlEasyPrinting.xml"/> + <xi:include href="../specs/specs_wxHtmlLinkEvent.xml"/> + <xi:include href="../specs/specs_wxHtmlWindow.xml"/> + <xi:include href="../specs/specs_wxIconBundle.xml"/> + <xi:include href="../specs/specs_wxIcon.xml"/> + <xi:include href="../specs/specs_wxIconizeEvent.xml"/> + <xi:include href="../specs/specs_wxIdleEvent.xml"/> + <xi:include href="../specs/specs_wxImage.xml"/> + <xi:include href="../specs/specs_wxImageList.xml"/> + <xi:include href="../specs/specs_wxInitDialogEvent.xml"/> + <xi:include href="../specs/specs_wxJoystickEvent.xml"/> + <xi:include href="../specs/specs_wxKeyEvent.xml"/> + <xi:include href="../specs/specs_wxLayoutAlgorithm.xml"/> + <xi:include href="../specs/specs_wxListbook.xml"/> + <xi:include href="../specs/specs_wxListBox.xml"/> + <xi:include href="../specs/specs_wxListCtrl.xml"/> + <xi:include href="../specs/specs_wxListEvent.xml"/> + <xi:include href="../specs/specs_wxListItemAttr.xml"/> + <xi:include href="../specs/specs_wxListItem.xml"/> + <xi:include href="../specs/specs_wxListView.xml"/> + <xi:include href="../specs/specs_wxLocale.xml"/> + <xi:include href="../specs/specs_wxLogNull.xml"/> + <xi:include href="../specs/specs_wxMask.xml"/> + <xi:include href="../specs/specs_wxMaximizeEvent.xml"/> + <xi:include href="../specs/specs_wxMDIChildFrame.xml"/> + <xi:include href="../specs/specs_wxMDIClientWindow.xml"/> + <xi:include href="../specs/specs_wxMDIParentFrame.xml"/> + <xi:include href="../specs/specs_wxMemoryDC.xml"/> + <xi:include href="../specs/specs_wxMenuBar.xml"/> + <xi:include href="../specs/specs_wxMenu.xml"/> + <xi:include href="../specs/specs_wxMenuEvent.xml"/> + <xi:include href="../specs/specs_wxMenuItem.xml"/> + <xi:include href="../specs/specs_wxMessageDialog.xml"/> + <xi:include href="../specs/specs_wxMiniFrame.xml"/> + <xi:include href="../specs/specs_wxMirrorDC.xml"/> + <xi:include href="../specs/specs_wxMouseCaptureChangedEvent.xml"/> + <xi:include href="../specs/specs_wxMouseCaptureLostEvent.xml"/> + <xi:include href="../specs/specs_wxMouseEvent.xml"/> + <xi:include href="../specs/specs_wxMoveEvent.xml"/> + <xi:include href="../specs/specs_wxMultiChoiceDialog.xml"/> + <xi:include href="../specs/specs_wxNavigationKeyEvent.xml"/> + <xi:include href="../specs/specs_wxNotebook.xml"/> + <xi:include href="../specs/specs_wxNotebookEvent.xml"/> + <xi:include href="../specs/specs_wxNotifyEvent.xml"/> + <xi:include href="../specs/specs_wxOverlay.xml"/> + <xi:include href="../specs/specs_wxPageSetupDialogData.xml"/> + <xi:include href="../specs/specs_wxPageSetupDialog.xml"/> + <xi:include href="../specs/specs_wxPaintDC.xml"/> + <xi:include href="../specs/specs_wxPaintEvent.xml"/> + <xi:include href="../specs/specs_wxPaletteChangedEvent.xml"/> + <xi:include href="../specs/specs_wxPalette.xml"/> + <xi:include href="../specs/specs_wxPanel.xml"/> + <xi:include href="../specs/specs_wxPasswordEntryDialog.xml"/> + <xi:include href="../specs/specs_wxPen.xml"/> + <xi:include href="../specs/specs_wxPickerBase.xml"/> + <xi:include href="../specs/specs_wxPopupTransientWindow.xml"/> + <xi:include href="../specs/specs_wxPopupWindow.xml"/> + <xi:include href="../specs/specs_wxPostScriptDC.xml"/> + <xi:include href="../specs/specs_wxPreviewCanvas.xml"/> + <xi:include href="../specs/specs_wxPreviewControlBar.xml"/> + <xi:include href="../specs/specs_wxPreviewFrame.xml"/> + <xi:include href="../specs/specs_wxPrintData.xml"/> + <xi:include href="../specs/specs_wxPrintDialogData.xml"/> + <xi:include href="../specs/specs_wxPrintDialog.xml"/> + <xi:include href="../specs/specs_wxPrinter.xml"/> + <xi:include href="../specs/specs_wxPrintout.xml"/> + <xi:include href="../specs/specs_wxPrintPreview.xml"/> + <xi:include href="../specs/specs_wxProgressDialog.xml"/> + <xi:include href="../specs/specs_wxQueryNewPaletteEvent.xml"/> + <xi:include href="../specs/specs_wxRadioBox.xml"/> + <xi:include href="../specs/specs_wxRadioButton.xml"/> + <xi:include href="../specs/specs_wxRegion.xml"/> + <xi:include href="../specs/specs_wxSashEvent.xml"/> + <xi:include href="../specs/specs_wxSashLayoutWindow.xml"/> + <xi:include href="../specs/specs_wxSashWindow.xml"/> + <xi:include href="../specs/specs_wxScreenDC.xml"/> + <xi:include href="../specs/specs_wxScrollBar.xml"/> + <xi:include href="../specs/specs_wxScrolledWindow.xml"/> + <xi:include href="../specs/specs_wxScrollEvent.xml"/> + <xi:include href="../specs/specs_wxScrollWinEvent.xml"/> + <xi:include href="../specs/specs_wxSetCursorEvent.xml"/> + <xi:include href="../specs/specs_wxShowEvent.xml"/> + <xi:include href="../specs/specs_wxSingleChoiceDialog.xml"/> + <xi:include href="../specs/specs_wxSizeEvent.xml"/> + <xi:include href="../specs/specs_wxSizer.xml"/> + <xi:include href="../specs/specs_wxSizerFlags.xml"/> + <xi:include href="../specs/specs_wxSizerItem.xml"/> + <xi:include href="../specs/specs_wxSlider.xml"/> + <xi:include href="../specs/specs_wxSpinButton.xml"/> + <xi:include href="../specs/specs_wxSpinCtrl.xml"/> + <xi:include href="../specs/specs_wxSpinEvent.xml"/> + <xi:include href="../specs/specs_wxSplashScreen.xml"/> + <xi:include href="../specs/specs_wxSplitterEvent.xml"/> + <xi:include href="../specs/specs_wxSplitterWindow.xml"/> + <xi:include href="../specs/specs_wxStaticBitmap.xml"/> + <xi:include href="../specs/specs_wxStaticBox.xml"/> + <xi:include href="../specs/specs_wxStaticBoxSizer.xml"/> + <xi:include href="../specs/specs_wxStaticLine.xml"/> + <xi:include href="../specs/specs_wxStaticText.xml"/> + <xi:include href="../specs/specs_wxStatusBar.xml"/> + <xi:include href="../specs/specs_wxStdDialogButtonSizer.xml"/> + <xi:include href="../specs/specs_wxStyledTextCtrl.xml"/> + <xi:include href="../specs/specs_wxStyledTextEvent.xml"/> + <xi:include href="../specs/specs_wxSysColourChangedEvent.xml"/> + <xi:include href="../specs/specs_wxSystemOptions.xml"/> + <xi:include href="../specs/specs_wxSystemSettings.xml"/> + <xi:include href="../specs/specs_wxTaskBarIcon.xml"/> + <xi:include href="../specs/specs_wxTaskBarIconEvent.xml"/> + <xi:include href="../specs/specs_wxTextAttr.xml"/> + <xi:include href="../specs/specs_wxTextCtrl.xml"/> + <xi:include href="../specs/specs_wxTextDataObject.xml"/> + <xi:include href="../specs/specs_wxTextEntryDialog.xml"/> + <xi:include href="../specs/specs_wxToggleButton.xml"/> + <xi:include href="../specs/specs_wxToolBar.xml"/> + <xi:include href="../specs/specs_wxToolbook.xml"/> + <xi:include href="../specs/specs_wxToolTip.xml"/> + <xi:include href="../specs/specs_wxTopLevelWindow.xml"/> + <xi:include href="../specs/specs_wxTreebook.xml"/> + <xi:include href="../specs/specs_wxTreeCtrl.xml"/> + <xi:include href="../specs/specs_wxTreeEvent.xml"/> + <xi:include href="../specs/specs_wxUpdateUIEvent.xml"/> + <xi:include href="../specs/specs_wxWindowCreateEvent.xml"/> + <xi:include href="../specs/specs_wxWindowDC.xml"/> + <xi:include href="../specs/specs_wxWindowDestroyEvent.xml"/> + <xi:include href="../specs/specs_wxWindow.xml"/> + <xi:include href="../specs/specs_wxXmlResource.xml"/> + <xi:include href="../specs/specs_wx_misc.xml"/> + <xi:include href="../specs/specs_glu.xml"/> + <xi:include href="../specs/specs_gl.xml"/> +</specs> diff --git a/lib/wx/include/wx.hrl b/lib/wx/include/wx.hrl index af7cca7ed2..30baeebb18 100644 --- a/lib/wx/include/wx.hrl +++ b/lib/wx/include/wx.hrl @@ -33,94 +33,51 @@ %% Here comes the definitions of all event records. %% they contain the event type and possible some extra information. --record(wxInitDialog, {type :: wxInitDialogEventType()}). %% Callback event: {@link wxInitDialogEvent} --type wxInitDialogEventType() :: 'init_dialog'. --type wxInitDialog() :: #wxInitDialog{}. %% Callback event: {@link wxInitDialogEvent} - --record(wxClose, {type :: wxCloseEventType()}). %% Callback event: {@link wxCloseEvent} --type wxCloseEventType() :: 'close_window' | 'end_session' | 'query_end_session'. --type wxClose() :: #wxClose{}. %% Callback event: {@link wxCloseEvent} - --record(wxStyledText,{type :: wxStyledTextEventType(), %% Callback event: {@link wxStyledTextEvent} - position :: integer(), - key :: integer(), - modifiers :: integer(), - modificationType :: integer(), - text :: unicode:chardata(), - length :: integer(), - linesAdded :: integer(), - line :: integer(), - foldLevelNow :: integer(), - foldLevelPrev :: integer(), - margin :: integer(), - message :: integer(), - wParam :: integer(), - lParam :: integer(), - listType :: integer(), - x :: integer(), - y :: integer(), - dragText :: unicode:chardata(), - dragAllowMove :: boolean(), - dragResult :: wx:wx_enum()}). --type wxStyledTextEventType() :: 'stc_change' | 'stc_styleneeded' | 'stc_charadded' | 'stc_savepointreached' | 'stc_savepointleft' | 'stc_romodifyattempt' | 'stc_key' | 'stc_doubleclick' | 'stc_updateui' | 'stc_modified' | 'stc_macrorecord' | 'stc_marginclick' | 'stc_needshown' | 'stc_painted' | 'stc_userlistselection' | 'stc_uridropped' | 'stc_dwellstart' | 'stc_dwellend' | 'stc_start_drag' | 'stc_drag_over' | 'stc_do_drop' | 'stc_zoom' | 'stc_hotspot_click' | 'stc_hotspot_dclick' | 'stc_calltip_click' | 'stc_autocomp_selection'. --type wxStyledText() :: #wxStyledText{}. %% Callback event: {@link wxStyledTextEvent} - --record(wxFileDirPicker,{type :: wxFileDirPickerEventType(), %% Callback event: {@link wxFileDirPickerEvent} - path :: unicode:chardata()}). --type wxFileDirPickerEventType() :: 'command_filepicker_changed' | 'command_dirpicker_changed'. --type wxFileDirPicker() :: #wxFileDirPicker{}. %% Callback event: {@link wxFileDirPickerEvent} - --record(wxNotebook,{type :: wxNotebookEventType(), %% Callback event: {@link wxNotebookEvent} - nSel :: integer(), - nOldSel :: integer()}). --type wxNotebookEventType() :: 'command_notebook_page_changed' | 'command_notebook_page_changing'. --type wxNotebook() :: #wxNotebook{}. %% Callback event: {@link wxNotebookEvent} +-record(wxFocus,{type :: wxFocusEventType(), %% Callback event: {@link wxFocusEvent} + win :: wxWindow:wxWindow()}). +-type wxFocusEventType() :: 'set_focus' | 'kill_focus'. +-type wxFocus() :: #wxFocus{}. %% Callback event: {@link wxFocusEvent} --record(wxIdle, {type :: wxIdleEventType()}). %% Callback event: {@link wxIdleEvent} --type wxIdleEventType() :: 'idle'. --type wxIdle() :: #wxIdle{}. %% Callback event: {@link wxIdleEvent} +-record(wxCommand,{type :: wxCommandEventType(), %% Callback event: {@link wxCommandEvent} + cmdString :: unicode:chardata(), + commandInt :: integer(), + extraLong :: integer()}). +-type wxCommandEventType() :: 'command_button_clicked' | 'command_checkbox_clicked' | 'command_choice_selected' | 'command_listbox_selected' | 'command_listbox_doubleclicked' | 'command_text_updated' | 'command_text_enter' | 'command_menu_selected' | 'command_slider_updated' | 'command_radiobox_selected' | 'command_radiobutton_selected' | 'command_scrollbar_updated' | 'command_vlbox_selected' | 'command_combobox_selected' | 'command_tool_rclicked' | 'command_tool_enter' | 'command_checklistbox_toggled' | 'command_togglebutton_clicked' | 'command_left_click' | 'command_left_dclick' | 'command_right_click' | 'command_set_focus' | 'command_kill_focus' | 'command_enter'. +-type wxCommand() :: #wxCommand{}. %% Callback event: {@link wxCommandEvent} -record(wxColourPicker,{type :: wxColourPickerEventType(), %% Callback event: {@link wxColourPickerEvent} colour :: wx:wx_colour()}). -type wxColourPickerEventType() :: 'command_colourpicker_changed'. -type wxColourPicker() :: #wxColourPicker{}. %% Callback event: {@link wxColourPickerEvent} --record(wxSplitter, {type :: wxSplitterEventType()}). %% Callback event: {@link wxSplitterEvent} --type wxSplitterEventType() :: 'command_splitter_sash_pos_changed' | 'command_splitter_sash_pos_changing' | 'command_splitter_doubleclicked' | 'command_splitter_unsplit'. --type wxSplitter() :: #wxSplitter{}. %% Callback event: {@link wxSplitterEvent} - --record(wxSash,{type :: wxSashEventType(), %% Callback event: {@link wxSashEvent} - edge :: wx:wx_enum(), - dragRect :: {X::integer(), Y::integer(), W::integer(), H::integer()}, - dragStatus :: wx:wx_enum()}). --type wxSashEventType() :: 'sash_dragged'. --type wxSash() :: #wxSash{}. %% Callback event: {@link wxSashEvent} - -record(wxHelp, {type :: wxHelpEventType()}). %% Callback event: {@link wxHelpEvent} -type wxHelpEventType() :: 'help' | 'detailed_help'. -type wxHelp() :: #wxHelp{}. %% Callback event: {@link wxHelpEvent} --record(wxDisplayChanged, {type :: wxDisplayChangedEventType()}). %% Callback event: {@link wxDisplayChangedEvent} --type wxDisplayChangedEventType() :: 'display_changed'. --type wxDisplayChanged() :: #wxDisplayChanged{}. %% Callback event: {@link wxDisplayChangedEvent} - --record(wxMouseCaptureLost, {type :: wxMouseCaptureLostEventType()}). %% Callback event: {@link wxMouseCaptureLostEvent} --type wxMouseCaptureLostEventType() :: 'mouse_capture_lost'. --type wxMouseCaptureLost() :: #wxMouseCaptureLost{}. %% Callback event: {@link wxMouseCaptureLostEvent} +-record(wxSpin,{type :: wxSpinEventType(), %% Callback event: {@link wxSpinEvent} + commandInt :: integer()}). +-type wxSpinEventType() :: 'command_spinctrl_updated' | 'spin_up' | 'spin_down' | 'spin'. +-type wxSpin() :: #wxSpin{}. %% Callback event: {@link wxSpinEvent} --record(wxFontPicker,{type :: wxFontPickerEventType(), %% Callback event: {@link wxFontPickerEvent} - font :: wxFont:wxFont()}). --type wxFontPickerEventType() :: 'command_fontpicker_changed'. --type wxFontPicker() :: #wxFontPicker{}. %% Callback event: {@link wxFontPickerEvent} +-record(wxAuiManager,{type :: wxAuiManagerEventType(), %% Callback event: {@link wxAuiManagerEvent} + manager :: wxAuiManager:wxAuiManager(), + pane :: wxAuiPaneInfo:wxAuiPaneInfo(), + button :: integer(), + veto_flag :: boolean(), + canveto_flag :: boolean(), + dc :: wxDC:wxDC()}). +-type wxAuiManagerEventType() :: 'aui_pane_button' | 'aui_pane_close' | 'aui_pane_maximize' | 'aui_pane_restore' | 'aui_pane_activated' | 'aui_render' | 'aui_find_manager'. +-type wxAuiManager() :: #wxAuiManager{}. %% Callback event: {@link wxAuiManagerEvent} --record(wxFocus,{type :: wxFocusEventType(), %% Callback event: {@link wxFocusEvent} - win :: wxWindow:wxWindow()}). --type wxFocusEventType() :: 'set_focus' | 'kill_focus'. --type wxFocus() :: #wxFocus{}. %% Callback event: {@link wxFocusEvent} +-record(wxDate,{type :: wxDateEventType(), %% Callback event: {@link wxDateEvent} + date :: wx:wx_datetime()}). +-type wxDateEventType() :: 'date_changed'. +-type wxDate() :: #wxDate{}. %% Callback event: {@link wxDateEvent} --record(wxPaletteChanged, {type :: wxPaletteChangedEventType()}). %% Callback event: {@link wxPaletteChangedEvent} --type wxPaletteChangedEventType() :: 'palette_changed'. --type wxPaletteChanged() :: #wxPaletteChanged{}. %% Callback event: {@link wxPaletteChangedEvent} +-record(wxIconize,{type :: wxIconizeEventType(), %% Callback event: {@link wxIconizeEvent} + iconized :: boolean()}). +-type wxIconizeEventType() :: 'iconize'. +-type wxIconize() :: #wxIconize{}. %% Callback event: {@link wxIconizeEvent} -record(wxScroll,{type :: wxScrollEventType(), %% Callback event: {@link wxScrollEvent} commandInt :: integer(), @@ -128,38 +85,41 @@ -type wxScrollEventType() :: 'scroll_top' | 'scroll_bottom' | 'scroll_lineup' | 'scroll_linedown' | 'scroll_pageup' | 'scroll_pagedown' | 'scroll_thumbtrack' | 'scroll_thumbrelease' | 'scroll_changed'. -type wxScroll() :: #wxScroll{}. %% Callback event: {@link wxScrollEvent} --record(wxChildFocus, {type :: wxChildFocusEventType()}). %% Callback event: {@link wxChildFocusEvent} --type wxChildFocusEventType() :: 'child_focus'. --type wxChildFocus() :: #wxChildFocus{}. %% Callback event: {@link wxChildFocusEvent} +-record(wxSplitter, {type :: wxSplitterEventType()}). %% Callback event: {@link wxSplitterEvent} +-type wxSplitterEventType() :: 'command_splitter_sash_pos_changed' | 'command_splitter_sash_pos_changing' | 'command_splitter_doubleclicked' | 'command_splitter_unsplit'. +-type wxSplitter() :: #wxSplitter{}. %% Callback event: {@link wxSplitterEvent} --record(wxAuiNotebook,{type :: wxAuiNotebookEventType(), %% Callback event: {@link wxAuiNotebookEvent} - old_selection :: integer(), - selection :: integer(), - drag_source :: wxAuiNotebook:wxAuiNotebook()}). --type wxAuiNotebookEventType() :: 'command_auinotebook_page_close' | 'command_auinotebook_page_changed' | 'command_auinotebook_page_changing' | 'command_auinotebook_button' | 'command_auinotebook_begin_drag' | 'command_auinotebook_end_drag' | 'command_auinotebook_drag_motion' | 'command_auinotebook_allow_dnd' | 'command_auinotebook_tab_middle_down' | 'command_auinotebook_tab_middle_up' | 'command_auinotebook_tab_right_down' | 'command_auinotebook_tab_right_up' | 'command_auinotebook_page_closed' | 'command_auinotebook_drag_done' | 'command_auinotebook_bg_dclick'. --type wxAuiNotebook() :: #wxAuiNotebook{}. %% Callback event: {@link wxAuiNotebookEvent} +-record(wxPaletteChanged, {type :: wxPaletteChangedEventType()}). %% Callback event: {@link wxPaletteChangedEvent} +-type wxPaletteChangedEventType() :: 'palette_changed'. +-type wxPaletteChanged() :: #wxPaletteChanged{}. %% Callback event: {@link wxPaletteChangedEvent} --record(wxSize,{type :: wxSizeEventType(), %% Callback event: {@link wxSizeEvent} - size :: {W::integer(), H::integer()}, - rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). --type wxSizeEventType() :: 'size'. --type wxSize() :: #wxSize{}. %% Callback event: {@link wxSizeEvent} +-record(wxNotebook,{type :: wxNotebookEventType(), %% Callback event: {@link wxNotebookEvent} + nSel :: integer(), + nOldSel :: integer()}). +-type wxNotebookEventType() :: 'command_notebook_page_changed' | 'command_notebook_page_changing'. +-type wxNotebook() :: #wxNotebook{}. %% Callback event: {@link wxNotebookEvent} --record(wxCommand,{type :: wxCommandEventType(), %% Callback event: {@link wxCommandEvent} - cmdString :: unicode:chardata(), - commandInt :: integer(), - extraLong :: integer()}). --type wxCommandEventType() :: 'command_button_clicked' | 'command_checkbox_clicked' | 'command_choice_selected' | 'command_listbox_selected' | 'command_listbox_doubleclicked' | 'command_text_updated' | 'command_text_enter' | 'command_menu_selected' | 'command_slider_updated' | 'command_radiobox_selected' | 'command_radiobutton_selected' | 'command_scrollbar_updated' | 'command_vlbox_selected' | 'command_combobox_selected' | 'command_tool_rclicked' | 'command_tool_enter' | 'command_checklistbox_toggled' | 'command_togglebutton_clicked' | 'command_left_click' | 'command_left_dclick' | 'command_right_click' | 'command_set_focus' | 'command_kill_focus' | 'command_enter'. --type wxCommand() :: #wxCommand{}. %% Callback event: {@link wxCommandEvent} +-record(wxContextMenu,{type :: wxContextMenuEventType(), %% Callback event: {@link wxContextMenuEvent} + pos :: {X::integer(), Y::integer()}}). +-type wxContextMenuEventType() :: 'context_menu'. +-type wxContextMenu() :: #wxContextMenu{}. %% Callback event: {@link wxContextMenuEvent} --record(wxMaximize, {type :: wxMaximizeEventType()}). %% Callback event: {@link wxMaximizeEvent} --type wxMaximizeEventType() :: 'maximize'. --type wxMaximize() :: #wxMaximize{}. %% Callback event: {@link wxMaximizeEvent} +-record(wxFontPicker,{type :: wxFontPickerEventType(), %% Callback event: {@link wxFontPickerEvent} + font :: wxFont:wxFont()}). +-type wxFontPickerEventType() :: 'command_fontpicker_changed'. +-type wxFontPicker() :: #wxFontPicker{}. %% Callback event: {@link wxFontPickerEvent} --record(wxSpin,{type :: wxSpinEventType(), %% Callback event: {@link wxSpinEvent} - commandInt :: integer()}). --type wxSpinEventType() :: 'command_spinctrl_updated' | 'spin_up' | 'spin_down' | 'spin'. --type wxSpin() :: #wxSpin{}. %% Callback event: {@link wxSpinEvent} +-record(wxChildFocus, {type :: wxChildFocusEventType()}). %% Callback event: {@link wxChildFocusEvent} +-type wxChildFocusEventType() :: 'child_focus'. +-type wxChildFocus() :: #wxChildFocus{}. %% Callback event: {@link wxChildFocusEvent} + +-record(wxTaskBarIcon, {type :: wxTaskBarIconEventType()}). %% Callback event: {@link wxTaskBarIconEvent} +-type wxTaskBarIconEventType() :: 'taskbar_move' | 'taskbar_left_down' | 'taskbar_left_up' | 'taskbar_right_down' | 'taskbar_right_up' | 'taskbar_left_dclick' | 'taskbar_right_dclick'. +-type wxTaskBarIcon() :: #wxTaskBarIcon{}. %% Callback event: {@link wxTaskBarIconEvent} + +-record(wxWindowDestroy, {type :: wxWindowDestroyEventType()}). %% Callback event: {@link wxWindowDestroyEvent} +-type wxWindowDestroyEventType() :: 'destroy'. +-type wxWindowDestroy() :: #wxWindowDestroy{}. %% Callback event: {@link wxWindowDestroyEvent} -record(wxMenu,{type :: wxMenuEventType(), %% Callback event: {@link wxMenuEvent} menuId :: integer(), @@ -167,30 +127,63 @@ -type wxMenuEventType() :: 'menu_open' | 'menu_close' | 'menu_highlight'. -type wxMenu() :: #wxMenu{}. %% Callback event: {@link wxMenuEvent} +-record(wxActivate,{type :: wxActivateEventType(), %% Callback event: {@link wxActivateEvent} + active :: boolean()}). +-type wxActivateEventType() :: 'activate' | 'activate_app' | 'hibernate'. +-type wxActivate() :: #wxActivate{}. %% Callback event: {@link wxActivateEvent} + +-record(wxGrid,{type :: wxGridEventType(), %% Callback event: {@link wxGridEvent} + row :: integer(), + col :: integer(), + x :: integer(), + y :: integer(), + selecting :: boolean(), + control :: boolean(), + meta :: boolean(), + shift :: boolean(), + alt :: boolean()}). +-type wxGridEventType() :: 'grid_cell_left_click' | 'grid_cell_right_click' | 'grid_cell_left_dclick' | 'grid_cell_right_dclick' | 'grid_label_left_click' | 'grid_label_right_click' | 'grid_label_left_dclick' | 'grid_label_right_dclick' | 'grid_row_size' | 'grid_col_size' | 'grid_range_select' | 'grid_cell_change' | 'grid_select_cell' | 'grid_editor_shown' | 'grid_editor_hidden' | 'grid_editor_created' | 'grid_cell_begin_drag'. +-type wxGrid() :: #wxGrid{}. %% Callback event: {@link wxGridEvent} + +-record(wxPaint, {type :: wxPaintEventType()}). %% Callback event: {@link wxPaintEvent} +-type wxPaintEventType() :: 'paint'. +-type wxPaint() :: #wxPaint{}. %% Callback event: {@link wxPaintEvent} + -record(wxShow,{type :: wxShowEventType(), %% Callback event: {@link wxShowEvent} show :: boolean()}). -type wxShowEventType() :: 'show'. -type wxShow() :: #wxShow{}. %% Callback event: {@link wxShowEvent} --record(wxWindowDestroy, {type :: wxWindowDestroyEventType()}). %% Callback event: {@link wxWindowDestroyEvent} --type wxWindowDestroyEventType() :: 'destroy'. --type wxWindowDestroy() :: #wxWindowDestroy{}. %% Callback event: {@link wxWindowDestroyEvent} - --record(wxContextMenu,{type :: wxContextMenuEventType(), %% Callback event: {@link wxContextMenuEvent} - pos :: {X::integer(), Y::integer()}}). --type wxContextMenuEventType() :: 'context_menu'. --type wxContextMenu() :: #wxContextMenu{}. %% Callback event: {@link wxContextMenuEvent} - --record(wxActivate,{type :: wxActivateEventType(), %% Callback event: {@link wxActivateEvent} - active :: boolean()}). --type wxActivateEventType() :: 'activate' | 'activate_app' | 'hibernate'. --type wxActivate() :: #wxActivate{}. %% Callback event: {@link wxActivateEvent} +-record(wxStyledText,{type :: wxStyledTextEventType(), %% Callback event: {@link wxStyledTextEvent} + position :: integer(), + key :: integer(), + modifiers :: integer(), + modificationType :: integer(), + text :: unicode:chardata(), + length :: integer(), + linesAdded :: integer(), + line :: integer(), + foldLevelNow :: integer(), + foldLevelPrev :: integer(), + margin :: integer(), + message :: integer(), + wParam :: integer(), + lParam :: integer(), + listType :: integer(), + x :: integer(), + y :: integer(), + dragText :: unicode:chardata(), + dragAllowMove :: boolean(), + dragResult :: wx:wx_enum()}). +-type wxStyledTextEventType() :: 'stc_change' | 'stc_styleneeded' | 'stc_charadded' | 'stc_savepointreached' | 'stc_savepointleft' | 'stc_romodifyattempt' | 'stc_key' | 'stc_doubleclick' | 'stc_updateui' | 'stc_modified' | 'stc_macrorecord' | 'stc_marginclick' | 'stc_needshown' | 'stc_painted' | 'stc_userlistselection' | 'stc_uridropped' | 'stc_dwellstart' | 'stc_dwellend' | 'stc_start_drag' | 'stc_drag_over' | 'stc_do_drop' | 'stc_zoom' | 'stc_hotspot_click' | 'stc_hotspot_dclick' | 'stc_calltip_click' | 'stc_autocomp_selection'. +-type wxStyledText() :: #wxStyledText{}. %% Callback event: {@link wxStyledTextEvent} --record(wxMove,{type :: wxMoveEventType(), %% Callback event: {@link wxMoveEvent} - pos :: {X::integer(), Y::integer()}, - rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). --type wxMoveEventType() :: 'move'. --type wxMove() :: #wxMove{}. %% Callback event: {@link wxMoveEvent} +-record(wxAuiNotebook,{type :: wxAuiNotebookEventType(), %% Callback event: {@link wxAuiNotebookEvent} + old_selection :: integer(), + selection :: integer(), + drag_source :: wxAuiNotebook:wxAuiNotebook()}). +-type wxAuiNotebookEventType() :: 'command_auinotebook_page_close' | 'command_auinotebook_page_changed' | 'command_auinotebook_page_changing' | 'command_auinotebook_button' | 'command_auinotebook_begin_drag' | 'command_auinotebook_end_drag' | 'command_auinotebook_drag_motion' | 'command_auinotebook_allow_dnd' | 'command_auinotebook_tab_middle_down' | 'command_auinotebook_tab_middle_up' | 'command_auinotebook_tab_right_down' | 'command_auinotebook_tab_right_up' | 'command_auinotebook_page_closed' | 'command_auinotebook_drag_done' | 'command_auinotebook_bg_dclick'. +-type wxAuiNotebook() :: #wxAuiNotebook{}. %% Callback event: {@link wxAuiNotebookEvent} -record(wxList,{type :: wxListEventType(), %% Callback event: {@link wxListEvent} code :: integer(), @@ -201,9 +194,9 @@ -type wxListEventType() :: 'command_list_begin_drag' | 'command_list_begin_rdrag' | 'command_list_begin_label_edit' | 'command_list_end_label_edit' | 'command_list_delete_item' | 'command_list_delete_all_items' | 'command_list_key_down' | 'command_list_insert_item' | 'command_list_col_click' | 'command_list_col_right_click' | 'command_list_col_begin_drag' | 'command_list_col_dragging' | 'command_list_col_end_drag' | 'command_list_item_selected' | 'command_list_item_deselected' | 'command_list_item_right_click' | 'command_list_item_middle_click' | 'command_list_item_activated' | 'command_list_item_focused' | 'command_list_cache_hint'. -type wxList() :: #wxList{}. %% Callback event: {@link wxListEvent} --record(wxClipboardText, {type :: wxClipboardTextEventType()}). %% Callback event: {@link wxClipboardTextEvent} --type wxClipboardTextEventType() :: 'command_text_copy' | 'command_text_cut' | 'command_text_paste'. --type wxClipboardText() :: #wxClipboardText{}. %% Callback event: {@link wxClipboardTextEvent} +-record(wxUpdateUI, {type :: wxUpdateUIEventType()}). %% Callback event: {@link wxUpdateUIEvent} +-type wxUpdateUIEventType() :: 'update_ui'. +-type wxUpdateUI() :: #wxUpdateUI{}. %% Callback event: {@link wxUpdateUIEvent} -record(wxScrollWin,{type :: wxScrollWinEventType(), %% Callback event: {@link wxScrollWinEvent} commandInt :: integer(), @@ -211,92 +204,81 @@ -type wxScrollWinEventType() :: 'scrollwin_top' | 'scrollwin_bottom' | 'scrollwin_lineup' | 'scrollwin_linedown' | 'scrollwin_pageup' | 'scrollwin_pagedown' | 'scrollwin_thumbtrack' | 'scrollwin_thumbrelease'. -type wxScrollWin() :: #wxScrollWin{}. %% Callback event: {@link wxScrollWinEvent} --record(wxIconize,{type :: wxIconizeEventType(), %% Callback event: {@link wxIconizeEvent} - iconized :: boolean()}). --type wxIconizeEventType() :: 'iconize'. --type wxIconize() :: #wxIconize{}. %% Callback event: {@link wxIconizeEvent} +-record(wxJoystick,{type :: wxJoystickEventType(), %% Callback event: {@link wxJoystickEvent} + pos :: {X::integer(), Y::integer()}, + zPosition :: integer(), + buttonChange :: integer(), + buttonState :: integer(), + joyStick :: integer()}). +-type wxJoystickEventType() :: 'joy_button_down' | 'joy_button_up' | 'joy_move' | 'joy_zmove'. +-type wxJoystick() :: #wxJoystick{}. %% Callback event: {@link wxJoystickEvent} --record(wxUpdateUI, {type :: wxUpdateUIEventType()}). %% Callback event: {@link wxUpdateUIEvent} --type wxUpdateUIEventType() :: 'update_ui'. --type wxUpdateUI() :: #wxUpdateUI{}. %% Callback event: {@link wxUpdateUIEvent} +-record(wxWindowCreate, {type :: wxWindowCreateEventType()}). %% Callback event: {@link wxWindowCreateEvent} +-type wxWindowCreateEventType() :: 'create'. +-type wxWindowCreate() :: #wxWindowCreate{}. %% Callback event: {@link wxWindowCreateEvent} --record(wxMouse,{type :: wxMouseEventType(), %% Callback event: {@link wxMouseEvent} +-record(wxClose, {type :: wxCloseEventType()}). %% Callback event: {@link wxCloseEvent} +-type wxCloseEventType() :: 'close_window' | 'end_session' | 'query_end_session'. +-type wxClose() :: #wxClose{}. %% Callback event: {@link wxCloseEvent} + +-record(wxKey,{type :: wxKeyEventType(), %% Callback event: {@link wxKeyEvent} x :: integer(), y :: integer(), - leftDown :: boolean(), - middleDown :: boolean(), - rightDown :: boolean(), + keyCode :: integer(), controlDown :: boolean(), shiftDown :: boolean(), altDown :: boolean(), metaDown :: boolean(), - wheelRotation :: integer(), - wheelDelta :: integer(), - linesPerAction :: integer()}). --type wxMouseEventType() :: 'left_down' | 'left_up' | 'middle_down' | 'middle_up' | 'right_down' | 'right_up' | 'motion' | 'enter_window' | 'leave_window' | 'left_dclick' | 'middle_dclick' | 'right_dclick' | 'mousewheel'. --type wxMouse() :: #wxMouse{}. %% Callback event: {@link wxMouseEvent} - --record(wxTree,{type :: wxTreeEventType(), %% Callback event: {@link wxTreeEvent} - item :: integer(), - itemOld :: integer(), - pointDrag :: {X::integer(), Y::integer()}}). --type wxTreeEventType() :: 'command_tree_begin_drag' | 'command_tree_begin_rdrag' | 'command_tree_begin_label_edit' | 'command_tree_end_label_edit' | 'command_tree_delete_item' | 'command_tree_get_info' | 'command_tree_set_info' | 'command_tree_item_expanded' | 'command_tree_item_expanding' | 'command_tree_item_collapsed' | 'command_tree_item_collapsing' | 'command_tree_sel_changed' | 'command_tree_sel_changing' | 'command_tree_key_down' | 'command_tree_item_activated' | 'command_tree_item_right_click' | 'command_tree_item_middle_click' | 'command_tree_end_drag' | 'command_tree_state_image_click' | 'command_tree_item_gettooltip' | 'command_tree_item_menu'. --type wxTree() :: #wxTree{}. %% Callback event: {@link wxTreeEvent} - --record(wxSysColourChanged, {type :: wxSysColourChangedEventType()}). %% Callback event: {@link wxSysColourChangedEvent} --type wxSysColourChangedEventType() :: 'sys_colour_changed'. --type wxSysColourChanged() :: #wxSysColourChanged{}. %% Callback event: {@link wxSysColourChangedEvent} + scanCode :: boolean(), + uniChar :: integer(), + rawCode :: integer(), + rawFlags :: integer()}). +-type wxKeyEventType() :: 'char' | 'char_hook' | 'key_down' | 'key_up'. +-type wxKey() :: #wxKey{}. %% Callback event: {@link wxKeyEvent} --record(wxNavigationKey,{type :: wxNavigationKeyEventType(), %% Callback event: {@link wxNavigationKeyEvent} - flags :: integer(), - focus :: wxWindow:wxWindow()}). --type wxNavigationKeyEventType() :: 'navigation_key'. --type wxNavigationKey() :: #wxNavigationKey{}. %% Callback event: {@link wxNavigationKeyEvent} +-record(wxIdle, {type :: wxIdleEventType()}). %% Callback event: {@link wxIdleEvent} +-type wxIdleEventType() :: 'idle'. +-type wxIdle() :: #wxIdle{}. %% Callback event: {@link wxIdleEvent} -record(wxQueryNewPalette, {type :: wxQueryNewPaletteEventType()}). %% Callback event: {@link wxQueryNewPaletteEvent} -type wxQueryNewPaletteEventType() :: 'query_new_palette'. -type wxQueryNewPalette() :: #wxQueryNewPalette{}. %% Callback event: {@link wxQueryNewPaletteEvent} --record(wxMouseCaptureChanged, {type :: wxMouseCaptureChangedEventType()}). %% Callback event: {@link wxMouseCaptureChangedEvent} --type wxMouseCaptureChangedEventType() :: 'mouse_capture_changed'. --type wxMouseCaptureChanged() :: #wxMouseCaptureChanged{}. %% Callback event: {@link wxMouseCaptureChangedEvent} - -record(wxHtmlLink,{type :: wxHtmlLinkEventType(), %% Callback event: {@link wxHtmlLinkEvent} linkInfo :: wx:wx_wxHtmlLinkInfo()}). -type wxHtmlLinkEventType() :: 'command_html_link_clicked'. -type wxHtmlLink() :: #wxHtmlLink{}. %% Callback event: {@link wxHtmlLinkEvent} --record(wxKey,{type :: wxKeyEventType(), %% Callback event: {@link wxKeyEvent} - x :: integer(), - y :: integer(), - keyCode :: integer(), - controlDown :: boolean(), - shiftDown :: boolean(), - altDown :: boolean(), - metaDown :: boolean(), - scanCode :: boolean(), - uniChar :: integer(), - rawCode :: integer(), - rawFlags :: integer()}). --type wxKeyEventType() :: 'char' | 'char_hook' | 'key_down' | 'key_up'. --type wxKey() :: #wxKey{}. %% Callback event: {@link wxKeyEvent} +-record(wxInitDialog, {type :: wxInitDialogEventType()}). %% Callback event: {@link wxInitDialogEvent} +-type wxInitDialogEventType() :: 'init_dialog'. +-type wxInitDialog() :: #wxInitDialog{}. %% Callback event: {@link wxInitDialogEvent} --record(wxTaskBarIcon, {type :: wxTaskBarIconEventType()}). %% Callback event: {@link wxTaskBarIconEvent} --type wxTaskBarIconEventType() :: 'taskbar_move' | 'taskbar_left_down' | 'taskbar_left_up' | 'taskbar_right_down' | 'taskbar_right_up' | 'taskbar_left_dclick' | 'taskbar_right_dclick'. --type wxTaskBarIcon() :: #wxTaskBarIcon{}. %% Callback event: {@link wxTaskBarIconEvent} +-record(wxMaximize, {type :: wxMaximizeEventType()}). %% Callback event: {@link wxMaximizeEvent} +-type wxMaximizeEventType() :: 'maximize'. +-type wxMaximize() :: #wxMaximize{}. %% Callback event: {@link wxMaximizeEvent} --record(wxGrid,{type :: wxGridEventType(), %% Callback event: {@link wxGridEvent} - row :: integer(), - col :: integer(), - x :: integer(), - y :: integer(), - selecting :: boolean(), - control :: boolean(), - meta :: boolean(), - shift :: boolean(), - alt :: boolean()}). --type wxGridEventType() :: 'grid_cell_left_click' | 'grid_cell_right_click' | 'grid_cell_left_dclick' | 'grid_cell_right_dclick' | 'grid_label_left_click' | 'grid_label_right_click' | 'grid_label_left_dclick' | 'grid_label_right_dclick' | 'grid_row_size' | 'grid_col_size' | 'grid_range_select' | 'grid_cell_change' | 'grid_select_cell' | 'grid_editor_shown' | 'grid_editor_hidden' | 'grid_editor_created' | 'grid_cell_begin_drag'. --type wxGrid() :: #wxGrid{}. %% Callback event: {@link wxGridEvent} +-record(wxClipboardText, {type :: wxClipboardTextEventType()}). %% Callback event: {@link wxClipboardTextEvent} +-type wxClipboardTextEventType() :: 'command_text_copy' | 'command_text_cut' | 'command_text_paste'. +-type wxClipboardText() :: #wxClipboardText{}. %% Callback event: {@link wxClipboardTextEvent} + +-record(wxTree,{type :: wxTreeEventType(), %% Callback event: {@link wxTreeEvent} + item :: integer(), + itemOld :: integer(), + pointDrag :: {X::integer(), Y::integer()}}). +-type wxTreeEventType() :: 'command_tree_begin_drag' | 'command_tree_begin_rdrag' | 'command_tree_begin_label_edit' | 'command_tree_end_label_edit' | 'command_tree_delete_item' | 'command_tree_get_info' | 'command_tree_set_info' | 'command_tree_item_expanded' | 'command_tree_item_expanding' | 'command_tree_item_collapsed' | 'command_tree_item_collapsing' | 'command_tree_sel_changed' | 'command_tree_sel_changing' | 'command_tree_key_down' | 'command_tree_item_activated' | 'command_tree_item_right_click' | 'command_tree_item_middle_click' | 'command_tree_end_drag' | 'command_tree_state_image_click' | 'command_tree_item_gettooltip' | 'command_tree_item_menu'. +-type wxTree() :: #wxTree{}. %% Callback event: {@link wxTreeEvent} + +-record(wxErase,{type :: wxEraseEventType(), %% Callback event: {@link wxEraseEvent} + dc :: wxDC:wxDC()}). +-type wxEraseEventType() :: 'erase_background'. +-type wxErase() :: #wxErase{}. %% Callback event: {@link wxEraseEvent} + +-record(wxSash,{type :: wxSashEventType(), %% Callback event: {@link wxSashEvent} + edge :: wx:wx_enum(), + dragRect :: {X::integer(), Y::integer(), W::integer(), H::integer()}, + dragStatus :: wx:wx_enum()}). +-type wxSashEventType() :: 'sash_dragged'. +-type wxSash() :: #wxSash{}. %% Callback event: {@link wxSashEvent} -record(wxCalendar,{type :: wxCalendarEventType(), %% Callback event: {@link wxCalendarEvent} wday :: wx:wx_enum(), @@ -304,42 +286,52 @@ -type wxCalendarEventType() :: 'calendar_sel_changed' | 'calendar_day_changed' | 'calendar_month_changed' | 'calendar_year_changed' | 'calendar_doubleclicked' | 'calendar_weekday_clicked'. -type wxCalendar() :: #wxCalendar{}. %% Callback event: {@link wxCalendarEvent} --record(wxWindowCreate, {type :: wxWindowCreateEventType()}). %% Callback event: {@link wxWindowCreateEvent} --type wxWindowCreateEventType() :: 'create'. --type wxWindowCreate() :: #wxWindowCreate{}. %% Callback event: {@link wxWindowCreateEvent} +-record(wxMouse,{type :: wxMouseEventType(), %% Callback event: {@link wxMouseEvent} + x :: integer(), + y :: integer(), + leftDown :: boolean(), + middleDown :: boolean(), + rightDown :: boolean(), + controlDown :: boolean(), + shiftDown :: boolean(), + altDown :: boolean(), + metaDown :: boolean(), + wheelRotation :: integer(), + wheelDelta :: integer(), + linesPerAction :: integer()}). +-type wxMouseEventType() :: 'left_down' | 'left_up' | 'middle_down' | 'middle_up' | 'right_down' | 'right_up' | 'motion' | 'enter_window' | 'leave_window' | 'left_dclick' | 'middle_dclick' | 'right_dclick' | 'mousewheel'. +-type wxMouse() :: #wxMouse{}. %% Callback event: {@link wxMouseEvent} --record(wxDate,{type :: wxDateEventType(), %% Callback event: {@link wxDateEvent} - date :: wx:wx_datetime()}). --type wxDateEventType() :: 'date_changed'. --type wxDate() :: #wxDate{}. %% Callback event: {@link wxDateEvent} +-record(wxSize,{type :: wxSizeEventType(), %% Callback event: {@link wxSizeEvent} + size :: {W::integer(), H::integer()}, + rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). +-type wxSizeEventType() :: 'size'. +-type wxSize() :: #wxSize{}. %% Callback event: {@link wxSizeEvent} --record(wxAuiManager,{type :: wxAuiManagerEventType(), %% Callback event: {@link wxAuiManagerEvent} - manager :: wxAuiManager:wxAuiManager(), - pane :: wxAuiPaneInfo:wxAuiPaneInfo(), - button :: integer(), - veto_flag :: boolean(), - canveto_flag :: boolean(), - dc :: wxDC:wxDC()}). --type wxAuiManagerEventType() :: 'aui_pane_button' | 'aui_pane_close' | 'aui_pane_maximize' | 'aui_pane_restore' | 'aui_pane_activated' | 'aui_render' | 'aui_find_manager'. --type wxAuiManager() :: #wxAuiManager{}. %% Callback event: {@link wxAuiManagerEvent} +-record(wxSysColourChanged, {type :: wxSysColourChangedEventType()}). %% Callback event: {@link wxSysColourChangedEvent} +-type wxSysColourChangedEventType() :: 'sys_colour_changed'. +-type wxSysColourChanged() :: #wxSysColourChanged{}. %% Callback event: {@link wxSysColourChangedEvent} --record(wxJoystick,{type :: wxJoystickEventType(), %% Callback event: {@link wxJoystickEvent} +-record(wxDisplayChanged, {type :: wxDisplayChangedEventType()}). %% Callback event: {@link wxDisplayChangedEvent} +-type wxDisplayChangedEventType() :: 'display_changed'. +-type wxDisplayChanged() :: #wxDisplayChanged{}. %% Callback event: {@link wxDisplayChangedEvent} + +-record(wxMove,{type :: wxMoveEventType(), %% Callback event: {@link wxMoveEvent} pos :: {X::integer(), Y::integer()}, - zPosition :: integer(), - buttonChange :: integer(), - buttonState :: integer(), - joyStick :: integer()}). --type wxJoystickEventType() :: 'joy_button_down' | 'joy_button_up' | 'joy_move' | 'joy_zmove'. --type wxJoystick() :: #wxJoystick{}. %% Callback event: {@link wxJoystickEvent} + rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). +-type wxMoveEventType() :: 'move'. +-type wxMove() :: #wxMove{}. %% Callback event: {@link wxMoveEvent} --record(wxPaint, {type :: wxPaintEventType()}). %% Callback event: {@link wxPaintEvent} --type wxPaintEventType() :: 'paint'. --type wxPaint() :: #wxPaint{}. %% Callback event: {@link wxPaintEvent} +-record(wxNavigationKey,{type :: wxNavigationKeyEventType(), %% Callback event: {@link wxNavigationKeyEvent} + flags :: integer(), + focus :: wxWindow:wxWindow()}). +-type wxNavigationKeyEventType() :: 'navigation_key'. +-type wxNavigationKey() :: #wxNavigationKey{}. %% Callback event: {@link wxNavigationKeyEvent} --record(wxErase,{type :: wxEraseEventType(), %% Callback event: {@link wxEraseEvent} - dc :: wxDC:wxDC()}). --type wxEraseEventType() :: 'erase_background'. --type wxErase() :: #wxErase{}. %% Callback event: {@link wxEraseEvent} +-record(wxFileDirPicker,{type :: wxFileDirPickerEventType(), %% Callback event: {@link wxFileDirPickerEvent} + path :: unicode:chardata()}). +-type wxFileDirPickerEventType() :: 'command_filepicker_changed' | 'command_dirpicker_changed'. +-type wxFileDirPicker() :: #wxFileDirPicker{}. %% Callback event: {@link wxFileDirPickerEvent} -record(wxSetCursor,{type :: wxSetCursorEventType(), %% Callback event: {@link wxSetCursorEvent} x :: integer(), @@ -348,6 +340,14 @@ -type wxSetCursorEventType() :: 'set_cursor'. -type wxSetCursor() :: #wxSetCursor{}. %% Callback event: {@link wxSetCursorEvent} +-record(wxMouseCaptureChanged, {type :: wxMouseCaptureChangedEventType()}). %% Callback event: {@link wxMouseCaptureChangedEvent} +-type wxMouseCaptureChangedEventType() :: 'mouse_capture_changed'. +-type wxMouseCaptureChanged() :: #wxMouseCaptureChanged{}. %% Callback event: {@link wxMouseCaptureChangedEvent} + +-record(wxMouseCaptureLost, {type :: wxMouseCaptureLostEventType()}). %% Callback event: {@link wxMouseCaptureLostEvent} +-type wxMouseCaptureLostEventType() :: 'mouse_capture_lost'. +-type wxMouseCaptureLost() :: #wxMouseCaptureLost{}. %% Callback event: {@link wxMouseCaptureLostEvent} + -type event() :: wxActivate() | wxAuiManager() | wxAuiNotebook() | wxCalendar() | wxChildFocus() | wxClipboardText() | wxClose() | wxColourPicker() | wxCommand() | wxContextMenu() | wxDate() | wxDisplayChanged() | wxErase() | wxFileDirPicker() | wxFocus() | wxFontPicker() | wxGrid() | wxHelp() | wxHtmlLink() | wxIconize() | wxIdle() | wxInitDialog() | wxJoystick() | wxKey() | wxList() | wxMaximize() | wxMenu() | wxMouse() | wxMouseCaptureChanged() | wxMouseCaptureLost() | wxMove() | wxNavigationKey() | wxNotebook() | wxPaint() | wxPaletteChanged() | wxQueryNewPalette() | wxSash() | wxScroll() | wxScrollWin() | wxSetCursor() | wxShow() | wxSize() | wxSpin() | wxSplitter() | wxStyledText() | wxSysColourChanged() | wxTaskBarIcon() | wxTree() | wxUpdateUI() | wxWindowCreate() | wxWindowDestroy(). -type wxEventType() :: wxActivateEventType() | wxAuiManagerEventType() | wxAuiNotebookEventType() | wxCalendarEventType() | wxChildFocusEventType() | wxClipboardTextEventType() | wxCloseEventType() | wxColourPickerEventType() | wxCommandEventType() | wxContextMenuEventType() | wxDateEventType() | wxDisplayChangedEventType() | wxEraseEventType() | wxFileDirPickerEventType() | wxFocusEventType() | wxFontPickerEventType() | wxGridEventType() | wxHelpEventType() | wxHtmlLinkEventType() | wxIconizeEventType() | wxIdleEventType() | wxInitDialogEventType() | wxJoystickEventType() | wxKeyEventType() | wxListEventType() | wxMaximizeEventType() | wxMenuEventType() | wxMouseCaptureChangedEventType() | wxMouseCaptureLostEventType() | wxMouseEventType() | wxMoveEventType() | wxNavigationKeyEventType() | wxNotebookEventType() | wxPaintEventType() | wxPaletteChangedEventType() | wxQueryNewPaletteEventType() | wxSashEventType() | wxScrollEventType() | wxScrollWinEventType() | wxSetCursorEventType() | wxShowEventType() | wxSizeEventType() | wxSpinEventType() | wxSplitterEventType() | wxStyledTextEventType() | wxSysColourChangedEventType() | wxTaskBarIconEventType() | wxTreeEventType() | wxUpdateUIEventType() | wxWindowCreateEventType() | wxWindowDestroyEventType(). diff --git a/lib/wx/src/gen/wxListCtrl.erl b/lib/wx/src/gen/wxListCtrl.erl index d1a063d900..851686062a 100644 --- a/lib/wx/src/gen/wxListCtrl.erl +++ b/lib/wx/src/gen/wxListCtrl.erl @@ -94,31 +94,34 @@ parent_class(_Class) -> erlang:error({badtype, ?MODULE}). -type wxListCtrl() :: wx:wx_object(). -%% @spec () -> wxListCtrl() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new() -> wxListCtrl(). new() -> wxe_util:construct(?wxListCtrl_new_0, <<>>). -%% @spec (Parent::wxWindow:wxWindow()) -> wxListCtrl() -%% @equiv new(Parent, []) +-spec new(Parent) -> wxListCtrl() when + Parent::wxWindow:wxWindow(). new(Parent) when is_record(Parent, wx_ref) -> new(Parent, []). -%% @spec (Parent::wxWindow:wxWindow(), [Option]) -> wxListCtrl() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} +%% @doc Creates a listctrl with optional callback functions: %% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() +%% OnGetItemText = (This, Item, Column) -> unicode:charlist() +%% OnGetItemAttr = (This, Item) -> wxListItemAttr:wxListItemAttr() %% OnGetItemColumnImage = (This, Item, Column) -> integer() -%% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +%% +%% See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new(Parent, [Option]) -> wxListCtrl() when + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. new(#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options)-> @@ -185,26 +188,27 @@ clearAll(#wx_ref{type=ThisT,ref=ThisRef}) -> <<ThisRef:32/?UI>>). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow()) -> bool() %% @equiv create(This,Parent, []) +-spec create(This, Parent) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(). create(This,Parent) when is_record(This, wx_ref),is_record(Parent, wx_ref) -> create(This,Parent, []). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow(), [Option]) -> bool() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} -%% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() -%% OnGetItemColumnImage = (This, Item, Column) -> integer() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlcreate">external documentation</a>. +-spec create(This, Parent, [Option]) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. + create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options) -> ?CLASS(ThisT,wxListCtrl), diff --git a/lib/wx/src/gen/wxXmlResource.erl b/lib/wx/src/gen/wxXmlResource.erl index ae02c74751..aa65b8b04e 100644 --- a/lib/wx/src/gen/wxXmlResource.erl +++ b/lib/wx/src/gen/wxXmlResource.erl @@ -334,8 +334,6 @@ unload(#wx_ref{type=ThisT,ref=ThisRef},Filename) <<ThisRef:32/?UI,(byte_size(Filename_UC)):32/?UI,(Filename_UC)/binary, 0:(((8- ((0+byte_size(Filename_UC)) band 16#7)) band 16#7))/unit:8>>). -%% @spec (Window::wxWindow:wxWindow(),Name::string(), Type::atom()) -> wx:wxObject() - %% @doc Looks up a control with Name in a window created with XML %% resources. You can use it to set/get values from controls. %% The object is type casted to <b>Type</b>. @@ -345,6 +343,10 @@ unload(#wx_ref{type=ThisT,ref=ThisRef},Filename) %% true = wxXmlResource:loadDialog(Xrc, Dlg, Frame, "controls_dialog"), <br /> %% LCtrl = xrcctrl(Dlg, "controls_listctrl", wxListCtrl), <br /> %% wxListCtrl:insertColumn(LCtrl, 0, "Name", [{width, 200}]), <br /> +-spec xrcctrl(Window, Name, Type) -> wx:wx_object() when + Window::wxWindow:wxWindow(), + Name::string(), + Type::atom(). xrcctrl(Window = #wx_ref{}, Name, Type) when is_list(Name), is_atom(Type) -> %% Func Id ?wxXmlResource_xrcctrl diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl index 40ee308358..40170b6eb1 100644 --- a/lib/wx/src/wx_object.erl +++ b/lib/wx/src/wx_object.erl @@ -55,7 +55,7 @@ %% When stop is returned in one of the functions above with Reason = %% normal | shutdown | Term, terminate(State) is called. It lets the %% user module clean up, it is always called when server terminates or -%% when wxObject() in the driver is deleted. If the Parent process +%% when wx_object() in the driver is deleted. If the Parent process %% terminates the Module:terminate/2 function is called. <br/> %% terminate(Reason, State) %% @@ -171,58 +171,59 @@ %% ----------------------------------------------------------------- -%% @spec (Mod, Args, Options) -> wxWindow:wxWindow() -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). - -%% @spec (Name, Mod, Args, Options) -> wxWindow:wxWindow() -%% Name = {local, atom()} -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug + %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Name::{local, atom()}, + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). -%% @spec (Mod, Args, Options) -> wxWindow:wxWindow() -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start_link(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). -%% @spec (Name, Mod, Args, Options) -> wxWindow:wxWindow() -%% Name = {local, atom()} -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start_link(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Name::{local, atom()}, + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). - + gen_response({ok, Pid}) -> receive {ack, Pid, Ref = #wx_ref{}} -> Ref end; gen_response(Reply) -> Reply. -%% @spec (Ref::wxObject()|atom()|pid()) -> ok %% @doc Stops a generic wx_object server with reason 'normal'. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the process does not exist, an %% exception is raised. +-spec stop(Obj) -> ok when + Obj::wx:wx_object()|atom()|pid(). stop(Ref = #wx_ref{state=Pid}) when is_pid(Pid) -> try gen:stop(Pid) @@ -236,11 +237,14 @@ stop(Name) when is_atom(Name) orelse is_pid(Name) -> erlang:error({ExitReason, {?MODULE, stop, [Name]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Reason::term(), Timeout::timeout()) -> ok %% @doc Stops a generic wx_object server with the given Reason. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the call times out, or if the process %% does not exist, an exception is raised. +-spec stop(Obj, Reason, Timeout) -> ok when + Obj::wx:wx_object()|atom()|pid(), + Reason::term(), + Timeout::timeout(). stop(Ref = #wx_ref{state=Pid}, Reason, Timeout) when is_pid(Pid) -> try gen:stop(Pid, Reason, Timeout) @@ -254,12 +258,14 @@ stop(Name, Reason, Timeout) when is_atom(Name) orelse is_pid(Name) -> erlang:error({ExitReason, {?MODULE, stop, [Name, Reason, Timeout]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Request::term()) -> term() %% @doc Make a call to a wx_object server. %% The call waits until it gets a result. %% Invokes handle_call(Request, From, State) in the server +-spec call(Obj, Request) -> term() when + Obj::wx:wx_object()|atom()|pid(), + Request::term(). call(Ref = #wx_ref{state=Pid}, Request) when is_pid(Pid) -> - try + try {ok,Res} = gen:call(Pid, '$gen_call', Request, infinity), Res catch _:Reason -> @@ -272,10 +278,13 @@ call(Name, Request) when is_atom(Name) orelse is_pid(Name) -> catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Name, Request]}}) end. - -%% @spec (Ref::wxObject()|atom()|pid(), Request::term(), Timeout::integer()) -> term() + %% @doc Make a call to a wx_object server with a timeout. %% Invokes handle_call(Request, From, State) in server +-spec call(Obj, Request, Timeout) -> term() when + Obj::wx:wx_object()|atom()|pid(), + Request::term(), + Timeout::integer(). call(Ref = #wx_ref{state=Pid}, Request, Timeout) when is_pid(Pid) -> try {ok,Res} = gen:call(Pid, '$gen_call', Request, Timeout), @@ -291,10 +300,11 @@ call(Name, Request, Timeout) when is_atom(Name) orelse is_pid(Name) -> erlang:error({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Request::term()) -> ok %% @doc Make a cast to a wx_object server. %% Invokes handle_cast(Request, State) in the server - +-spec cast(Obj, Request) -> ok when + Obj::wx:wx_object()|atom()|pid(), + Request::term(). cast(#wx_ref{state=Pid}, Request) when is_pid(Pid) -> Pid ! {'$gen_cast',Request}, ok; @@ -302,21 +312,23 @@ cast(Name, Request) when is_atom(Name) orelse is_pid(Name) -> Name ! {'$gen_cast',Request}, ok. -%% @spec (Ref::wxObject()) -> pid() %% @doc Get the pid of the object handle. +-spec get_pid(Obj) -> pid() when + Obj::wx:wx_object()|atom()|pid(). get_pid(#wx_ref{state=Pid}) when is_pid(Pid) -> Pid. -%% @spec (Ref::wxObject(), pid()) -> wxObject() %% @doc Sets the controlling process of the object handle. +-spec set_pid(Obj, pid()) -> wx:wx_object() when + Obj::wx:wx_object()|atom()|pid(). set_pid(#wx_ref{}=R, Pid) when is_pid(Pid) -> R#wx_ref{state=Pid}. %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- -%% @spec (From::tuple(), Reply::term()) -> pid() %% @doc Get the pid of the object handle. +-spec reply({pid(), Tag::term()}, Reply::term()) -> pid(). reply({To, Tag}, Reply) -> catch To ! {Tag, Reply}. diff --git a/lib/xmerl/src/xmerl_eventp.erl b/lib/xmerl/src/xmerl_eventp.erl index 2cb76abc6e..8d7ea25e24 100644 --- a/lib/xmerl/src/xmerl_eventp.erl +++ b/lib/xmerl/src/xmerl_eventp.erl @@ -25,6 +25,90 @@ %% Each contain more elaborate settings of xmerl_scan that makes usage of %% the customization functions. %% +%% @type xmlElement() = #xmlElement{}. +%% +%% @type option_list(). <p>Options allow to customize the behaviour of the +%% scanner. +%% See also <a href="xmerl_examples.html">tutorial</a> on customization +%% functions. +%% </p> +%% <p> +%% Possible options are: +%% </p> +%% <dl> +%% <dt><code>{acc_fun, Fun}</code></dt> +%% <dd>Call back function to accumulate contents of entity.</dd> +%% <dt><code>{continuation_fun, Fun} | +%% {continuation_fun, Fun, ContinuationState}</code></dt> +%% <dd>Call back function to decide what to do if the scanner runs into EOF +%% before the document is complete.</dd> +%% <dt><code>{event_fun, Fun} | +%% {event_fun, Fun, EventState}</code></dt> +%% <dd>Call back function to handle scanner events.</dd> +%% <dt><code>{fetch_fun, Fun} | +%% {fetch_fun, Fun, FetchState}</code></dt> +%% <dd>Call back function to fetch an external resource.</dd> +%% <dt><code>{hook_fun, Fun} | +%% {hook_fun, Fun, HookState}</code></dt> +%% <dd>Call back function to process the document entities once +%% identified.</dd> +%% <dt><code>{close_fun, Fun}</code></dt> +%% <dd>Called when document has been completely parsed.</dd> +%% <dt><code>{rules, ReadFun, WriteFun, RulesState} | +%% {rules, Rules}</code></dt> +%% <dd>Handles storing of scanner information when parsing.</dd> +%% <dt><code>{user_state, UserState}</code></dt> +%% <dd>Global state variable accessible from all customization functions</dd> +%% +%% <dt><code>{fetch_path, PathList}</code></dt> +%% <dd>PathList is a list of +%% directories to search when fetching files. If the file in question +%% is not in the fetch_path, the URI will be used as a file +%% name.</dd> +%% <dt><code>{space, Flag}</code></dt> +%% <dd>'preserve' (default) to preserve spaces, 'normalize' to +%% accumulate consecutive whitespace and replace it with one space.</dd> +%% <dt><code>{line, Line}</code></dt> +%% <dd>To specify starting line for scanning in document which contains +%% fragments of XML.</dd> +%% <dt><code>{namespace_conformant, Flag}</code></dt> +%% <dd>Controls whether to behave as a namespace conformant XML parser, +%% 'false' (default) to not otherwise 'true'.</dd> +%% <dt><code>{validation, Flag}</code></dt> +%% <dd>Controls whether to process as a validating XML parser: +%% 'off' (default) no validation, or validation 'dtd' by DTD or 'schema' +%% by XML Schema. 'false' and 'true' options are obsolete +%% (i.e. they may be removed in a future release), if used 'false' +%% equals 'off' and 'true' equals 'dtd'.</dd> +%% <dt><code>{schemaLocation, [{Namespace,Link}|...]}</code></dt> +%% <dd>Tells explicitly which XML Schema documents to use to validate +%% the XML document. Used together with the +%% <code>{validation,schema}</code> option.</dd> +%% <dt><code>{quiet, Flag}</code></dt> +%% <dd>Set to 'true' if xmerl should behave quietly and not output any +%% information to standard output (default 'false').</dd> +%% <dt><code>{doctype_DTD, DTD}</code></dt> +%% <dd>Allows to specify DTD name when it isn't available in the XML +%% document. This option has effect only together with +%% <code>{validation,'dtd'</code> option.</dd> +%% <dt><code>{xmlbase, Dir}</code></dt> +%% <dd>XML Base directory. If using string/1 default is current directory. +%% If using file/1 default is directory of given file.</dd> +%% <dt><code>{encoding, Enc}</code></dt> +%% <dd>Set default character set used (default UTF-8). +%% This character set is used only if not explicitly given by the XML +%% declaration. </dd> +%% <dt><code>{document, Flag}</code></dt> +%% <dd>Set to 'true' if xmerl should return a complete XML document +%% as an xmlDocument record (default 'false').</dd> +%% <dt><code>{comments, Flag}</code></dt> +%% <dd>Set to 'false' if xmerl should skip comments otherwise they will +%% be returned as xmlComment records (default 'true').</dd> +%% <dt><code>{default_attrs, Flag}</code></dt> +%% <dd>Set to 'true' if xmerl should add to elements missing attributes +%% with a defined default value (default 'false').</dd> +%% </dl> +%% -module(xmerl_eventp). -vsn('0.19'). -date('03-09-17'). diff --git a/lib/xmerl/src/xmerl_scan.erl b/lib/xmerl/src/xmerl_scan.erl index 2147a46a13..5e0459ec21 100644 --- a/lib/xmerl/src/xmerl_scan.erl +++ b/lib/xmerl/src/xmerl_scan.erl @@ -111,13 +111,16 @@ %% <dd>Set to 'true' if xmerl should add to elements missing attributes %% with a defined default value (default 'false').</dd> %% </dl> +%% @type xmlElement() = #xmlElement{}. +%% The record definition is found in xmerl.hrl. +%% @type xmlDocument() = #xmlDocument{}. +%% The record definition is found in xmerl.hrl. %% @type document() = xmlElement() | xmlDocument(). <p> %% The document returned by <tt>xmerl_scan:string/[1,2]</tt> and %% <tt>xmerl_scan:file/[1,2]</tt>. The type of the returned record depends on %% the value of the document option passed to the function. %% </p> - -module(xmerl_scan). -vsn('0.20'). -date('03-09-16'). diff --git a/lib/xmerl/src/xmerl_xpath.erl b/lib/xmerl/src/xmerl_xpath.erl index bbebda1030..6146feba49 100644 --- a/lib/xmerl/src/xmerl_xpath.erl +++ b/lib/xmerl/src/xmerl_xpath.erl @@ -43,13 +43,27 @@ %% </pre> %% %% @type nodeEntity() = -%% xmlElement() -%% | xmlAttribute() -%% | xmlText() -%% | xmlPI() -%% | xmlComment() -%% | xmlNsNode() -%% | xmlDocument() +%% #xmlElement{} +%% | #xmlAttribute{} +%% | #xmlText{} +%% | #xmlPI{} +%% | #xmlComment{} +%% | #xmlNsNode{} +%% | #xmlDocument{} +%% +%% @type docNodes() = #xmlElement{} +%% | #xmlAttribute{} +%% | #xmlText{} +%% | #xmlPI{} +%% | #xmlComment{} +%% | #xmlNsNode{} +%% +%% @type docEntity() = #xmlDocument{} | [docNodes()] +%% +%% @type xPathString() = string() +%% +%% @type parentList() = [{atom(), integer()}] +%% %% @type option_list(). <p>Options allows to customize the behaviour of the %% XPath scanner. %% </p> @@ -115,7 +129,7 @@ string(Str, Doc, Options) -> %% Parents = parentList() %% Doc = nodeEntity() %% Options = option_list() -%% Scalar = xmlObj +%% Scalar = #xmlObj{} %% @doc Extracts the nodes from the parsed XML tree according to XPath. %% xmlObj is a record with fields type and value, %% where type is boolean | number | string diff --git a/lib/xmerl/src/xmerl_xs.erl b/lib/xmerl/src/xmerl_xs.erl index 3e9f6622b8..1ce76cfa41 100644 --- a/lib/xmerl/src/xmerl_xs.erl +++ b/lib/xmerl/src/xmerl_xs.erl @@ -45,7 +45,6 @@ % XSLT package which is written i C++. % See also the <a href="xmerl_xs_examples.html">Tutorial</a>. % </p> - -module(xmerl_xs). -export([xslapply/2, value_of/1, select/2, built_in_rules/2 ]). @@ -71,15 +70,13 @@ %% xslapply(fun template/1, E), %% "</h1>"]; %% </pre> - xslapply(Fun, EList) when is_list(EList) -> - lists:map( Fun, EList); + lists:map(Fun, EList); xslapply(Fun, E = #xmlElement{})-> lists:map( Fun, E#xmlElement.content). - %% @spec value_of(E) -> List -%% E = unknown() +%% E = term() %% %% @doc Concatenates all text nodes within the tree. %% diff --git a/lib/xmerl/src/xmerl_xsd.erl b/lib/xmerl/src/xmerl_xsd.erl index 4b5efae8dd..a89b3159ec 100644 --- a/lib/xmerl/src/xmerl_xsd.erl +++ b/lib/xmerl/src/xmerl_xsd.erl @@ -49,6 +49,7 @@ %% <dd>It is possible by this option to provide a state with process %% information from an earlier validation.</dd> %% </dl> +%% @type filename() = string() %% @end %%%------------------------------------------------------------------- -module(xmerl_xsd). @@ -138,7 +139,7 @@ state2file(S=#xsd_state{schema_name=SN}) -> %% @spec state2file(State,FileName) -> ok | {error,Reason} %% State = global_state() -%% FileName = filename() +%% FileName = string() %% @doc Saves the schema state with all information of the processed %% schema in a file. You can provide the file name for the saved %% state. FileName is saved with the <code>.xss</code> extension @@ -153,7 +154,7 @@ state2file(S,FileName) when is_record(S,xsd_state) -> %% @spec file2state(FileName) -> {ok,State} | {error,Reason} %% State = global_state() -%% FileName = filename() +%% FileName = string() %% @doc Reads the schema state with all information of the processed %% schema from a file created with <code>state2file/[1,2]</code>. The %% format of this file is internal. The state can then be used @@ -202,7 +203,7 @@ xmerl_xsd_vsn_check(S=#xsd_state{vsn=MD5_VSN}) -> process_validate(Schema,Xml) -> process_validate(Schema,Xml,[]). %% @spec process_validate(Schema,Element,Options) -> Result -%% Schema = filename() +%% Schema = string() %% Element = XmlElement %% Options = option_list() %% Result = {ValidXmlElement,State} | {error,Reason} @@ -282,7 +283,7 @@ validate3(_,_,S) -> process_schema(Schema) -> process_schema(Schema,[]). %% @spec process_schema(Schema,Options) -> Result -%% Schema = filename() +%% Schema = string() %% Result = {ok,State} | {error,Reason} %% State = global_state() %% Reason = [ErrorReason] | ErrorReason @@ -324,7 +325,7 @@ process_schema2({SE,_},State,_Schema) -> process_schemas(Schemas) -> process_schemas(Schemas,[]). %% @spec process_schemas(Schemas,Options) -> Result -%% Schemas = [{NameSpace,filename()}|Schemas] | [] +%% Schemas = [{NameSpace,string()}|Schemas] | [] %% Result = {ok,State} | {error,Reason} %% Reason = [ErrorReason] | ErrorReason %% Options = option_list() @@ -5426,7 +5427,7 @@ add_key_once(Key,N,El,L) -> %% {filename:join([[io_lib:format("/~w(~w)",[X,Y])||{X,Y}<-Parents],Type]),Pos}. %% @spec format_error(Errors) -> Result -%% Errors = error_tuple() | [error_tuple()] +%% Errors = tuple() | [tuple()] %% Result = string() | [string()] %% @doc Formats error descriptions to human readable strings. format_error(L) when is_list(L) -> diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk index a78a035a1f..95adaa5bb0 100644 --- a/lib/xmerl/vsn.mk +++ b/lib/xmerl/vsn.mk @@ -1 +1 @@ -XMERL_VSN = 1.3.11 +XMERL_VSN = 1.3.12 |